Remove legacy channel codecs and DB migrations (#3150)
What changed, and why it matters
This commit is a large cleanup of old code in the Eclair Lightning node software. It removes support for reading very old channel data formats (before version 0.13) and removes the built-in tool that migrated data from SQLite to PostgreSQL. The change is intentional and documented: users on versions older than 0.13 must first upgrade to 0.13, which will convert their data, before installing this newer version. There is no evidence of a security vulnerability being introduced; the main risk is that an unprepared operator could accidentally make their node fail to start by skipping the required intermediate upgrade.
Operators should verify their node is already on eclair v0.13 or later before deploying this release. If running an older version, upgrade to v0.13 first to allow channel DB migration, then upgrade to the newer release. Review release notes and backup the database before upgrading. No code-level security patch is required.
Security signals we found
Removal of legacy deserialization code reduces attack surface for malformed old channel states
Explicit minimum DB version check prevents partial/incompatible upgrades
No new parsing of untrusted network input introduced
No cryptographic changes except a new JSON serializer for an existing type
Evidence from the diff
The commit deletes legacy channel codecs v0-v4, the dual-database SQLite/Postgres migration subsystem (DualDatabases, MigrateDb, CompareDb and per-DB migration objects), and the corresponding DB migrations for the channels table. It adds an early version check (minimum channels DB version 7 for SQLite, 11 for Postgres) and a failing codec for legacy version bytes, both producing a clear error message telling operators to first run v0.13. The change also regenerates backwards-compatibility test vectors and adds a JSON serializer for MuSig2 nonces. It is a maintenance/deprecation patch, not a security fix or vulnerability.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scalaeclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0-4/ChannelCodecs*.scalaeclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scalaeclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scalaeclair-core/src/main/scala/fr/acinq/eclair/db/migration/*.scalaeclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scalaeclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scalaeclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scalaInspect captured patch +4713 / −8582
diff --git a/docs/PostgreSQL.md b/docs/PostgreSQL.md
index 315d45b..3039642 100644
--- a/docs/PostgreSQL.md
+++ b/docs/PostgreSQL.md
@@ -2,11 +2,11 @@
By default, Eclair stores its data on the machine's local file system (typically in `~/.eclair` directory) using SQLite.
-It also supports PostgreSQL version 10.6 and higher as a database backend.
+It also supports PostgreSQL version 10.6 and higher as a database backend.
To enable PostgreSQL support set the `driver` parameter to `postgres`:
-```
+```conf
eclair.db.driver = postgres
```
@@ -14,7 +14,7 @@ eclair.db.driver = postgres
To configure the connection settings use the `database`, `host`, `port` `username` and `password` parameters:
-```
+```conf
eclair.db.postgres.database = "mydb"
eclair.db.postgres.host = "127.0.0.1" # Default: "localhost"
eclair.db.postgres.port = 12345 # Default: 5432
@@ -22,14 +22,14 @@ eclair.db.postgres.username = "myuser"
eclair.db.postgres.password = "mypassword"
```
-Eclair uses Hikari connection pool (https://github.com/brettwooldridge/HikariCP) which has a lot of configuration
-parameters. Some of them can be set in Eclair config file. The most important is `pool.max-size`, it defines the maximum
-allowed number of simultaneous connections to the database.
+Eclair uses [Hikari connection pool](https://github.com/brettwooldridge/HikariCP) which has a lot of configuration parameters.
+Some of them can be set in Eclair config file.
+The most important is `pool.max-size`, it defines the maximum allowed number of simultaneous connections to the database.
-A good rule of thumb is to set `pool.max-size` to the CPU core count times 2.
-See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing for better estimation.
+A good rule of thumb is to set `pool.max-size` to the CPU core count times 2.
+See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing for better estimation.
-```
+```conf
eclair.db.postgres.pool {
max-size = 8 # Default: 10
connection-timeout = 10 seconds # Default: 30 seconds
@@ -39,18 +39,18 @@ eclair.db.postgres.pool {
```
### Locking settings
-
-Running multiple Eclair processes connected to the same database can lead to data corruption and loss of funds.
-That's why Eclair supports database locking mechanisms to prevent multiple Eclair instances from accessing one database together.
+
+Running multiple Eclair processes connected to the same database can lead to data corruption and loss of funds.
+That's why Eclair supports database locking mechanisms to prevent multiple Eclair instances from accessing one database together.
Use `postgres.lock-type` parameter to set the locking schemes.
- Lock type | Description
----|---
-`lease` | At the beginning, Eclair acquires a lease for the database that expires after some time. Then it constantly extends the lease. On each lease extension and each database transaction, Eclair checks if the lease belongs to the Eclair instance. If it doesn't, Eclair assumes that the database was updated by another Eclair process and terminates. Note that this is just a safeguard feature for Eclair rather than a bulletproof database-wide lock, because third-party applications still have the ability to access the database without honoring this locking scheme.
-`none` | No locking at all. Useful for tests. DO NOT USE ON MAINNET!
+ Lock type | Description
+-----------|----------------
+ `lease` | At the beginning, Eclair acquires a lease for the database that expires after some time. Then it constantly extends the lease. On each lease extension and each database transaction, Eclair checks if the lease belongs to the Eclair instance. If it doesn't, Eclair assumes that the database was updated by another Eclair process and terminates. Note that this is just a safeguard feature for Eclair rather than a bulletproof database-wide lock, because third-party applications still have the ability to access the database without honoring this locking scheme.
+ `none` | No locking at all. Useful for tests. DO NOT USE ON MAINNET!
-```
+```conf
eclair.db.postgres.lock-type = "none" // Default: "lease"
```
@@ -58,10 +58,9 @@ eclair.db.postgres.lock-type = "none" // Default: "lease"
There are two main configuration parameters for the lease locking scheme: `lease.interval` and `lease.renew-interval`.
`lease.interval` defines lease validity time. During the lease time no other node can acquire the lock, except the lease holder.
-After that time the lease is assumed expired, any node can acquire the lease. So that only one node can update the database
-at a time. Eclair extends the lease every `lease.renew-interval` until terminated.
+After that time the lease is assumed expired, any node can acquire the lease. So that only one node can update the database at a time. Eclair extends the lease every `lease.renew-interval` until terminated.
-```
+```conf
eclair.db.postgres.lease {
interval = 30 seconds // Default: 5 minutes
renew-interval = 10 seconds // Default: 1 minute
@@ -70,16 +69,13 @@ eclair.db.postgres.lease {
### Backups and replication
-The PostgreSQL driver doesn't support Eclair's built-in online backups. Instead, you should use the tools provided
-by PostgreSQL.
+The PostgreSQL driver doesn't support Eclair's built-in online backups. Instead, you should use the tools provided by PostgreSQL.
#### Backup/Restore
-For nodes with infrequent channel updates its easier to use `pg_dump` to perform the task.
+For nodes with infrequent channel updates its easier to use `pg_dump` to perform the task.
-It's important to stop the node to prevent any channel updates while a backup/restore operation is in progress. It makes
-sense to back up the database after each channel update, to prevent restoring an outdated channel's state and consequently
-losing the funds associated with that channel.
+It's important to stop the node to prevent any channel updates while a backup/restore operation is in progress. It makes sense to back up the database after each channel update, to prevent restoring an outdated channel's state and consequently losing the funds associated with that channel.
For more information about backup refer to the official PostgreSQL documentation: https://www.postgresql.org/docs/current/backup.html
@@ -87,58 +83,24 @@ For more information about backup refer to the official PostgreSQL documentation
For busier nodes it isn't practical to use `pg_dump`. Fortunately, PostgreSQL provides built-in database replication which makes the backup/restore process more seamless.
-To set up database replication you need to create a main database, that accepts all changes from the node, and a replica database.
-Once replication is configured, the main database will automatically send all the changes to the replica.
+To set up database replication you need to create a main database, that accepts all changes from the node, and a replica database.
+Once replication is configured, the main database will automatically send all the changes to the replica.
In case of failure of the main database, the node can be simply reconfigured to use the replica instead of the main database.
-PostgreSQL supports [different types of replication](https://www.postgresql.org/docs/current/different-replication-solutions.html).
-The most suitable type for an Eclair node is [synchronous streaming replication](https://www.postgresql.org/docs/current/warm-standby.html#SYNCHRONOUS-REPLICATION),
-because it provides a very important feature, that helps keep the replicated channel's state up to date:
+PostgreSQL supports [different types of replication](https://www.postgresql.org/docs/current/different-replication-solutions.html).
+The most suitable type for an Eclair node is [synchronous streaming replication](https://www.postgresql.org/docs/current/warm-standby.html#SYNCHRONOUS-REPLICATION), because it provides a very important feature, that helps keep the replicated channel's state up to date:
> When requesting synchronous replication, each commit of a write transaction will wait until confirmation is received that the commit has been written to the write-ahead log on disk of both the primary and standby server.
-Follow the official PostgreSQL high availability documentation for the instructions to set up synchronous streaming replication: https://www.postgresql.org/docs/current/high-availability.html
+Follow the official PostgreSQL [high availability documentation](https://www.postgresql.org/docs/current/high-availability.html) for the instructions to set up synchronous streaming replication.
### Safeguard to prevent accidental loss of funds due to database misconfiguration
Using Eclair with an outdated version of the database or a database created with another seed might lead to loss of funds.
-Every time Eclair starts, it checks if the Postgres database connection settings have changed since the last start.
-If in fact the settings have changed, Eclair stops immediately to prevent potentially dangerous
-but accidental configuration changes to come into effect.
-
-Eclair stores the latest database settings in the `${data-dir}/last_jdbcurl` file, and compares its contents with the database settings from the config file.
-
-The node operator can force Eclair to accept new database
-connection settings by removing the `last_jdbcurl` file.
-
-### Migrating from Sqlite to Postgres
-
-Eclair supports migrating your existing node from Sqlite to Postgres. Note that the opposite (from Postgres to Sqlite) is not supported.
-
-:warning: Once you have migrated from Sqlite to Postgres there is no going back!
-
-To migrate from Sqlite to Postgres, follow these steps:
-1. Stop Eclair
-2. Edit `eclair.conf`
- 1. Set `eclair.db.postgres.*` as explained in the section [Connection Settings](#connection-settings).
- 2. Set `eclair.db.driver=dual-sqlite-primary`. This will make Eclair use both databases backends. All calls to sqlite will be replicated in postgres.
- 3. Set `eclair.db.dual.migrate-on-restart=true`. This will make Eclair migrate the data from Sqlite to Postgres at startup.
- 4. Set `eclair.db.dual.compare-on-restart=true`. This will make Eclair compare Sqlite and Postgres at startup. The result of the comparison is displayed in the logs.
-3. Delete the file `~/.eclair/last_jdbcurl`. The purpose of this file is to prevent accidental change in the database backend.
-4. Start Eclair. You should see in the logs:
- 1. `migrating all tables...`
- 2. `migration complete`
- 3. `comparing all tables...`
- 4. `comparison complete identical=true` (NB: if `identical=false`, contact support)
-5. Eclair should then finish startup and operate normally. Data has been migrated to Postgres, and Sqlite/Postgres will be maintained in sync going forward.
-6. Edit `eclair.conf` and set `eclair.db.dual.migrate-on-restart=false` but do not restart Eclair yet.
-7. We recommend that you leave Eclair in dual db mode for a while, to make sure that you don't have issues with your new Postgres database. This a good time to set up [Backups and replication](#backups-and-replication).
-8. After some time has passed, restart Eclair. You should see in the logs:
- 1. `comparing all tables...`
- 2. `comparison complete identical=true` (NB: if `identical=false`, contact support)
-9. At this point we have confidence that the Postgres backend works normally, and we are ready to drop Sqlite for good.
-10. Edit `eclair.conf`
- 1. Set `eclair.db.driver=postgres`
- 2. Set `eclair.db.dual.compare-on-restart=false`
-11. Restart Eclair. From this moment, you cannot go back to Sqlite! If you try to do so, Eclair will refuse to start.
\ No newline at end of file
+Every time Eclair starts, it checks if the Postgres database connection settings have changed since the last start.
+If in fact the settings have changed, Eclair stops immediately to prevent potentially dangerous but accidental configuration changes to come into effect.
+
+Eclair stores the latest database settings in the `${data-dir}/last_jdbcurl` file, and compares its contents with the database settings from the config file.
+
+The node operator can force Eclair to accept new database connection settings by removing the `last_jdbcurl` file.
diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md
index 144875b..6decb73 100644
--- a/docs/release-notes/eclair-vnext.md
+++ b/docs/release-notes/eclair-vnext.md
@@ -6,6 +6,11 @@
<insert changes>
+### Remove support for legacy channel codecs
+
+We remove the code used to deserialize channel data from versions of eclair prior to v0.13.
+Node operators running a version of `eclair` older than v0.13 must first upgrade to v0.13 to migrate their channel data, and then upgrade to the latest version.
+
### Update minimal version of Bitcoin Core
With this release, eclair requires using Bitcoin Core 29.1.
diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf
index efbee29..f509dda 100644
--- a/eclair-core/src/main/resources/reference.conf
+++ b/eclair-core/src/main/resources/reference.conf
@@ -550,7 +550,7 @@ eclair {
}
db {
- driver = "sqlite" // sqlite, postgres, dual-sqlite-primary, dual-postgres-primary
+ driver = "sqlite" // sqlite, postgres
postgres {
database = "eclair"
host = "localhost"
@@ -592,10 +592,6 @@ eclair {
}
}
}
- dual {
- migrate-on-restart = false // migrate sqlite -> postgres on restart (only applies if sqlite is primary)
- compare-on-restart = false // compare sqlite and postgres dbs on restart (only applies if sqlite is primary)
- }
// During normal channel operation, we need to store information about past HTLCs to be able to punish our peer if
// they publish a revoked commitment. Once a channel closes or a splice transaction confirms, we can clean up past
// data (which reduces the size of our DB). Since there may be millions of rows to delete and we don't want to slow
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala
index c7f929f..5004549 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala
@@ -21,7 +21,6 @@ import akka.actor.{ActorSystem, CoordinatedShutdown}
import com.typesafe.config.Config
import com.zaxxer.hikari.{HikariConfig, HikariDataSource}
import fr.acinq.eclair.TimestampMilli
-import fr.acinq.eclair.db.migration.{CompareDb, MigrateDb}
import fr.acinq.eclair.db.pg.PgUtils.PgLock.LockFailureHandler
import fr.acinq.eclair.db.pg.PgUtils._
import fr.acinq.eclair.db.pg._
@@ -80,6 +79,8 @@ object Databases extends Logging {
object SqliteDatabases {
def apply(auditJdbc: Connection, networkJdbc: Connection, eclairJdbc: Connection, jdbcUrlFile_opt: Option[File]): SqliteDatabases = {
jdbcUrlFile_opt.foreach(checkIfDatabaseUrlIsUnchanged("sqlite", _))
+ // We check whether the node operator needs to run an intermediate eclair version first.
+ using(eclairJdbc.createStatement(), inTransaction = true) { statement => checkChannelsDbVersion(statement, SqliteChannelsDb.DB_NAME, minimum = 7) }
SqliteDatabases(
network = new SqliteNetworkDb(networkJdbc),
liquidity = new SqliteLiquidityDb(eclairJdbc),
@@ -154,6 +155,11 @@ object Databases extends Logging {
}
}
+ // We check whether the node operator needs to run an intermediate eclair version first.
+ PgUtils.inTransaction { connection =>
+ using(connection.createStatement()) { statement => checkChannelsDbVersion(statement, PgChannelsDb.DB_NAME, minimum = 11) }
+ }
+
val databases = PostgresDatabases(
network = new PgNetworkDb,
liquidity = new PgLiquidityDb,
@@ -281,21 +287,6 @@ object Databases extends Logging {
dbConfig.getString("driver") match {
case "sqlite" => Databases.sqlite(chaindir, jdbcUrlFile_opt = Some(jdbcUrlFile))
case "postgres" => Databases.postgres(dbConfig, instanceId, chaindir, jdbcUrlFile_opt = Some(jdbcUrlFile))
- case dual@("dual-sqlite-primary" | "dual-postgres-primary") =>
- logger.info(s"using $dual database mode")
- val sqlite = Databases.sqlite(chaindir, jdbcUrlFile_opt = None)
- val postgres = Databases.postgres(dbConfig, instanceId, chaindir, jdbcUrlFile_opt = None)
- val (primary, secondary) = if (dual == "dual-sqlite-primary") (sqlite, postgres) else (postgres, sqlite)
- val dualDb = DualDatabases(primary, secondary)
- if (primary == sqlite) {
- if (dbConfig.getBoolean("dual.migrate-on-restart")) {
- MigrateDb.migrateAll(dualDb)
- }
- if (dbConfig.getBoolean("dual.compare-on-restart")) {
- CompareDb.compareAll(dualDb)
- }
- }
- dualDb
case driver => throw new RuntimeException(s"unknown database driver `$driver`")
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala
deleted file mode 100644
index ec2b77b..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala
+++ /dev/null
@@ -1,523 +0,0 @@
-package fr.acinq.eclair.db
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder
-import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi, TxId}
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.db.AuditDb.PublishedTransaction
-import fr.acinq.eclair.db.Databases.{FileBackup, PostgresDatabases, SqliteDatabases}
-import fr.acinq.eclair.db.DbEventHandler.ChannelEvent
-import fr.acinq.eclair.db.DualDatabases.runAsync
-import fr.acinq.eclair.payment._
-import fr.acinq.eclair.payment.relay.OnTheFlyFunding
-import fr.acinq.eclair.payment.relay.Relayer.RelayFees
-import fr.acinq.eclair.router.Router
-import fr.acinq.eclair.wire.protocol._
-import fr.acinq.eclair.{CltvExpiry, Features, InitFeature, MilliSatoshi, Paginated, RealShortChannelId, ShortChannelId, TimestampMilli, TimestampSecond}
-import grizzled.slf4j.Logging
-import scodec.bits.ByteVector
-
-import java.io.File
-import java.util.UUID
-import java.util.concurrent.Executors
-import scala.collection.immutable.SortedMap
-import scala.concurrent.{ExecutionContext, Future}
-import scala.util.{Failure, Success, Try}
-
-/**
- * An implementation of [[Databases]] where there are two separate underlying db, one primary and one secondary.
- * All calls to primary are replicated asynchronously to secondary.
- * Calls to secondary are made asynchronously in a dedicated thread pool, so that it doesn't have any performance impact.
- */
-case class DualDatabases(primary: Databases, secondary: Databases) extends Databases with FileBackup {
-
- override val network: NetworkDb = DualNetworkDb(primary.network, secondary.network)
- override val audit: AuditDb = DualAuditDb(primary.audit, secondary.audit)
- override val channels: ChannelsDb = DualChannelsDb(primary.channels, secondary.channels)
- override val peers: PeersDb = DualPeersDb(primary.peers, secondary.peers)
- override val payments: PaymentsDb = DualPaymentsDb(primary.payments, secondary.payments)
- override val offers: OffersDb = DualOffersDb(primary.offers, secondary.offers)
- override val pendingCommands: PendingCommandsDb = DualPendingCommandsDb(primary.pendingCommands, secondary.pendingCommands)
- override val liquidity: LiquidityDb = DualLiquidityDb(primary.liquidity, secondary.liquidity)
-
- /** if one of the database supports file backup, we use it */
- override def backup(backupFile: File): Unit = (primary, secondary) match {
- case (f: FileBackup, _) => f.backup(backupFile)
- case (_, f: FileBackup) => f.backup(backupFile)
- case _ => ()
- }
-}
-
-object DualDatabases extends Logging {
-
- /** Run asynchronously and print errors */
- def runAsync[T](f: => T)(implicit ec: ExecutionContext): Future[T] = Future {
- Try(f) match {
- case Success(res) => res
- case Failure(t) =>
- logger.error("postgres error:\n", t)
- throw t
- }
- }
-
- def getDatabases(dualDatabases: DualDatabases): (SqliteDatabases, PostgresDatabases) =
- (dualDatabases.primary, dualDatabases.secondary) match {
- case (sqliteDb: SqliteDatabases, postgresDb: PostgresDatabases) =>
- (sqliteDb, postgresDb)
- case (postgresDb: PostgresDatabases, sqliteDb: SqliteDatabases) =>
- (sqliteDb, postgresDb)
- case _ => throw new IllegalArgumentException("there must be one sqlite and one postgres in dual db mode")
- }
-}
-
-case class DualNetworkDb(primary: NetworkDb, secondary: NetworkDb) extends NetworkDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-network").build()))
-
- override def addNode(n: NodeAnnouncement): Unit = {
- runAsync(secondary.addNode(n))
- primary.addNode(n)
- }
-
- override def updateNode(n: NodeAnnouncement): Unit = {
- runAsync(secondary.updateNode(n))
- primary.updateNode(n)
- }
-
- override def getNode(nodeId: Crypto.PublicKey): Option[NodeAnnouncement] = {
- runAsync(secondary.getNode(nodeId))
- primary.getNode(nodeId)
- }
-
- override def removeNode(nodeId: Crypto.PublicKey): Unit = {
- runAsync(secondary.removeNode(nodeId))
- primary.removeNode(nodeId)
- }
-
- override def listNodes(): Seq[NodeAnnouncement] = {
- runAsync(secondary.listNodes())
- primary.listNodes()
- }
-
- override def addChannel(c: ChannelAnnouncement, txid: TxId, capacity: Satoshi): Unit = {
- runAsync(secondary.addChannel(c, txid, capacity))
- primary.addChannel(c, txid, capacity)
- }
-
- override def updateChannel(u: ChannelUpdate): Unit = {
- runAsync(secondary.updateChannel(u))
- primary.updateChannel(u)
- }
-
- override def removeChannels(shortChannelIds: Iterable[ShortChannelId]): Unit = {
- runAsync(secondary.removeChannels(shortChannelIds))
- primary.removeChannels(shortChannelIds)
- }
-
- override def getChannel(shortChannelId: RealShortChannelId): Option[Router.PublicChannel] = {
- runAsync(secondary.getChannel(shortChannelId))
- primary.getChannel(shortChannelId)
- }
-
- override def listChannels(): SortedMap[RealShortChannelId, Router.PublicChannel] = {
- runAsync(secondary.listChannels())
- primary.listChannels()
- }
-
-}
-
-case class DualAuditDb(primary: AuditDb, secondary: AuditDb) extends AuditDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-audit").build()))
-
- override def add(channelLifecycle: DbEventHandler.ChannelEvent): Unit = {
- runAsync(secondary.add(channelLifecycle))
- primary.add(channelLifecycle)
- }
-
- override def add(paymentSent: PaymentSent): Unit = {
- runAsync(secondary.add(paymentSent))
- primary.add(paymentSent)
- }
-
- override def add(paymentReceived: PaymentReceived): Unit = {
- runAsync(secondary.add(paymentReceived))
- primary.add(paymentReceived)
- }
-
- override def add(paymentRelayed: PaymentRelayed): Unit = {
- runAsync(secondary.add(paymentRelayed))
- primary.add(paymentRelayed)
- }
-
- override def add(txPublished: TransactionPublished): Unit = {
- runAsync(secondary.add(txPublished))
- primary.add(txPublished)
- }
-
- override def add(txConfirmed: TransactionConfirmed): Unit = {
- runAsync(secondary.add(txConfirmed))
- primary.add(txConfirmed)
- }
-
- override def add(channelErrorOccurred: ChannelErrorOccurred): Unit = {
- runAsync(secondary.add(channelErrorOccurred))
- primary.add(channelErrorOccurred)
- }
-
- override def addChannelUpdate(channelUpdateParametersChanged: ChannelUpdateParametersChanged): Unit = {
- runAsync(secondary.addChannelUpdate(channelUpdateParametersChanged))
- primary.addChannelUpdate(channelUpdateParametersChanged)
- }
-
- override def addPathFindingExperimentMetrics(metrics: PathFindingExperimentMetrics): Unit = {
- runAsync(secondary.addPathFindingExperimentMetrics(metrics))
- primary.addPathFindingExperimentMetrics(metrics)
- }
-
- override def listPublished(channelId: ByteVector32): Seq[PublishedTransaction] = {
- runAsync(secondary.listPublished(channelId))
- primary.listPublished(channelId)
- }
-
- override def listSent(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[PaymentSent] = {
- runAsync(secondary.listSent(from, to, paginated_opt))
- primary.listSent(from, to, paginated_opt)
- }
-
- override def listReceived(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[PaymentReceived] = {
- runAsync(secondary.listReceived(from, to, paginated_opt))
- primary.listReceived(from, to, paginated_opt)
- }
-
- override def listRelayed(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[PaymentRelayed] = {
- runAsync(secondary.listRelayed(from, to, paginated_opt))
- primary.listRelayed(from, to, paginated_opt)
- }
-
- override def listNetworkFees(from: TimestampMilli, to: TimestampMilli): Seq[AuditDb.NetworkFee] = {
- runAsync(secondary.listNetworkFees(from, to))
- primary.listNetworkFees(from, to)
- }
-
- override def stats(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[AuditDb.Stats] = {
- runAsync(secondary.stats(from, to, paginated_opt))
- primary.stats(from, to, paginated_opt)
- }
-}
-
-case class DualChannelsDb(primary: ChannelsDb, secondary: ChannelsDb) extends ChannelsDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-channels").build()))
-
- override def addOrUpdateChannel(data: PersistentChannelData): Unit = {
- runAsync(secondary.addOrUpdateChannel(data))
- primary.addOrUpdateChannel(data)
- }
-
- override def getChannel(channelId: ByteVector32): Option[PersistentChannelData] = {
- runAsync(secondary.getChannel(channelId))
- primary.getChannel(channelId)
- }
-
- override def updateChannelMeta(channelId: ByteVector32, event: ChannelEvent.EventType): Unit = {
- runAsync(secondary.updateChannelMeta(channelId, event))
- primary.updateChannelMeta(channelId, event)
- }
-
- override def removeChannel(channelId: ByteVector32): Unit = {
- runAsync(secondary.removeChannel(channelId))
- primary.removeChannel(channelId)
- }
-
- override def markHtlcInfosForRemoval(channelId: ByteVector32, beforeCommitIndex: Long): Unit = {
- runAsync(secondary.markHtlcInfosForRemoval(channelId, beforeCommitIndex))
- primary.markHtlcInfosForRemoval(channelId, beforeCommitIndex)
- }
-
- override def removeHtlcInfos(batchSize: Int): Unit = {
- runAsync(secondary.removeHtlcInfos(batchSize))
- primary.removeHtlcInfos(batchSize)
- }
-
- override def listLocalChannels(): Seq[PersistentChannelData] = {
- runAsync(secondary.listLocalChannels())
- primary.listLocalChannels()
- }
-
- override def listClosedChannels(remoteNodeId_opt: Option[PublicKey], paginated_opt: Option[Paginated]): Seq[PersistentChannelData] = {
- runAsync(secondary.listClosedChannels(remoteNodeId_opt, paginated_opt))
- primary.listClosedChannels(remoteNodeId_opt, paginated_opt)
- }
-
- override def addHtlcInfo(channelId: ByteVector32, commitmentNumber: Long, paymentHash: ByteVector32, cltvExpiry: CltvExpiry): Unit = {
- runAsync(secondary.addHtlcInfo(channelId, commitmentNumber, paymentHash, cltvExpiry))
- primary.addHtlcInfo(channelId, commitmentNumber, paymentHash, cltvExpiry)
- }
-
- override def listHtlcInfos(channelId: ByteVector32, commitmentNumber: Long): Seq[(ByteVector32, CltvExpiry)] = {
- runAsync(secondary.listHtlcInfos(channelId, commitmentNumber))
- primary.listHtlcInfos(channelId, commitmentNumber)
- }
-}
-
-case class DualPeersDb(primary: PeersDb, secondary: PeersDb) extends PeersDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-peers").build()))
-
- override def addOrUpdatePeer(nodeId: Crypto.PublicKey, address: NodeAddress, features: Features[InitFeature]): Unit = {
- runAsync(secondary.addOrUpdatePeer(nodeId, address, features))
- primary.addOrUpdatePeer(nodeId, address, features)
- }
-
- override def addOrUpdatePeerFeatures(nodeId: Crypto.PublicKey, features: Features[InitFeature]): Unit = {
- runAsync(secondary.addOrUpdatePeerFeatures(nodeId, features))
- primary.addOrUpdatePeerFeatures(nodeId, features)
- }
-
- override def removePeer(nodeId: Crypto.PublicKey): Unit = {
- runAsync(secondary.removePeer(nodeId))
- primary.removePeer(nodeId)
- }
-
- override def getPeer(nodeId: Crypto.PublicKey): Option[NodeInfo] = {
- runAsync(secondary.getPeer(nodeId))
- primary.getPeer(nodeId)
- }
-
- override def listPeers(): Map[Crypto.PublicKey, NodeInfo] = {
- runAsync(secondary.listPeers())
- primary.listPeers()
- }
-
- override def addOrUpdateRelayFees(nodeId: Crypto.PublicKey, fees: RelayFees): Unit = {
- runAsync(secondary.addOrUpdateRelayFees(nodeId, fees))
- primary.addOrUpdateRelayFees(nodeId, fees)
- }
-
- override def getRelayFees(nodeId: Crypto.PublicKey): Option[RelayFees] = {
- runAsync(secondary.getRelayFees(nodeId))
- primary.getRelayFees(nodeId)
- }
-
- override def updateStorage(nodeId: PublicKey, data: ByteVector): Unit = {
- runAsync(secondary.updateStorage(nodeId, data))
- primary.updateStorage(nodeId, data)
- }
-
- override def getStorage(nodeId: PublicKey): Option[ByteVector] = {
- runAsync(secondary.getStorage(nodeId))
- primary.getStorage(nodeId)
- }
-
- override def removePeerStorage(peerRemovedBefore: TimestampSecond): Unit = {
- runAsync(secondary.removePeerStorage(peerRemovedBefore))
- primary.removePeerStorage(peerRemovedBefore)
- }
-}
-
-case class DualPaymentsDb(primary: PaymentsDb, secondary: PaymentsDb) extends PaymentsDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-payments").build()))
-
- override def addIncomingPayment(pr: Bolt11Invoice, preimage: ByteVector32, paymentType: String): Unit = {
- runAsync(secondary.addIncomingPayment(pr, preimage, paymentType))
- primary.addIncomingPayment(pr, preimage, paymentType)
- }
-
- override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli): Boolean = {
- runAsync(secondary.receiveIncomingPayment(paymentHash, amount, receivedAt))
- primary.receiveIncomingPayment(paymentHash, amount, receivedAt)
- }
-
- override def receiveIncomingOfferPayment(pr: MinimalBolt12Invoice, preimage: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli, paymentType: String): Unit = {
- runAsync(secondary.receiveIncomingOfferPayment(pr, preimage, amount, receivedAt, paymentType))
- primary.receiveIncomingOfferPayment(pr, preimage, amount, receivedAt, paymentType)
- }
-
- override def getIncomingPayment(paymentHash: ByteVector32): Option[IncomingPayment] = {
- runAsync(secondary.getIncomingPayment(paymentHash))
- primary.getIncomingPayment(paymentHash)
- }
-
- override def removeIncomingPayment(paymentHash: ByteVector32): Try[Unit] = {
- runAsync(secondary.removeIncomingPayment(paymentHash))
- primary.removeIncomingPayment(paymentHash)
- }
-
- override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
- runAsync(secondary.listIncomingPayments(from, to, paginated_opt))
- primary.listIncomingPayments(from, to, paginated_opt)
- }
-
- override def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
- runAsync(secondary.listPendingIncomingPayments(from, to, paginated_opt))
- primary.listPendingIncomingPayments(from, to, paginated_opt)
- }
-
- override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
- runAsync(secondary.listExpiredIncomingPayments(from, to, paginated_opt))
- primary.listExpiredIncomingPayments(from, to, paginated_opt)
- }
-
- override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
- runAsync(secondary.listReceivedIncomingPayments(from, to, paginated_opt))
- primary.listReceivedIncomingPayments(from, to, paginated_opt)
- }
-
- override def addOutgoingPayment(outgoingPayment: OutgoingPayment): Unit = {
- runAsync(secondary.addOutgoingPayment(outgoingPayment))
- primary.addOutgoingPayment(outgoingPayment)
- }
-
- override def updateOutgoingPayment(paymentResult: PaymentSent): Unit = {
- runAsync(secondary.updateOutgoingPayment(paymentResult))
- primary.updateOutgoingPayment(paymentResult)
- }
-
- override def updateOutgoingPayment(paymentResult: PaymentFailed): Unit = {
- runAsync(secondary.updateOutgoingPayment(paymentResult))
- primary.updateOutgoingPayment(paymentResult)
- }
-
- override def getOutgoingPayment(id: UUID): Option[OutgoingPayment] = {
- runAsync(secondary.getOutgoingPayment(id))
- primary.getOutgoingPayment(id)
- }
-
- override def listOutgoingPayments(parentId: UUID): Seq[OutgoingPayment] = {
- runAsync(secondary.listOutgoingPayments(parentId))
- primary.listOutgoingPayments(parentId)
- }
-
- override def listOutgoingPayments(paymentHash: ByteVector32): Seq[OutgoingPayment] = {
- runAsync(secondary.listOutgoingPayments(paymentHash))
- primary.listOutgoingPayments(paymentHash)
- }
-
- override def listOutgoingPayments(from: TimestampMilli, to: TimestampMilli): Seq[OutgoingPayment] = {
- runAsync(secondary.listOutgoingPayments(from, to))
- primary.listOutgoingPayments(from, to)
- }
-
- override def listOutgoingPaymentsToOffer(offerId: ByteVector32): Seq[OutgoingPayment] = {
- runAsync(secondary.listOutgoingPaymentsToOffer(offerId))
- primary.listOutgoingPaymentsToOffer(offerId)
- }
-}
-
-case class DualOffersDb(primary: OffersDb, secondary: OffersDb) extends OffersDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-offers").build()))
-
- override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli = TimestampMilli.now()): Option[OfferData] = {
- runAsync(secondary.addOffer(offer, pathId_opt, createdAt))
- primary.addOffer(offer, pathId_opt, createdAt)
- }
-
- override def disableOffer(offer: OfferTypes.Offer, disabledAt: TimestampMilli = TimestampMilli.now()): Unit = {
- runAsync(secondary.disableOffer(offer, disabledAt))
- primary.disableOffer(offer, disabledAt)
- }
-
- override def listOffers(onlyActive: Boolean): Seq[OfferData] = {
- runAsync(secondary.listOffers(onlyActive))
- primary.listOffers(onlyActive)
- }
-}
-
-case class DualPendingCommandsDb(primary: PendingCommandsDb, secondary: PendingCommandsDb) extends PendingCommandsDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-pending-commands").build()))
-
- override def addSettlementCommand(channelId: ByteVector32, cmd: HtlcSettlementCommand): Unit = {
- runAsync(secondary.addSettlementCommand(channelId, cmd))
- primary.addSettlementCommand(channelId, cmd)
- }
-
- override def removeSettlementCommand(channelId: ByteVector32, htlcId: Long): Unit = {
- runAsync(secondary.removeSettlementCommand(channelId, htlcId))
- primary.removeSettlementCommand(channelId, htlcId)
- }
-
- override def listSettlementCommands(channelId: ByteVector32): Seq[HtlcSettlementCommand] = {
- runAsync(secondary.listSettlementCommands(channelId))
- primary.listSettlementCommands(channelId)
- }
-
- override def listSettlementCommands(): Seq[(ByteVector32, HtlcSettlementCommand)] = {
- runAsync(secondary.listSettlementCommands())
- primary.listSettlementCommands()
- }
-}
-
-case class DualLiquidityDb(primary: LiquidityDb, secondary: LiquidityDb) extends LiquidityDb {
-
- private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-liquidity").build()))
-
- override def addPurchase(liquidityPurchase: ChannelLiquidityPurchased): Unit = {
- runAsync(secondary.addPurchase(liquidityPurchase))
- primary.addPurchase(liquidityPurchase)
- }
-
- override def setConfirmed(remoteNodeId: PublicKey, txId: TxId): Unit = {
- runAsync(secondary.setConfirmed(remoteNodeId, txId))
- primary.setConfirmed(remoteNodeId, txId)
- }
-
- override def listPurchases(remoteNodeId: PublicKey): Seq[LiquidityPurchase] = {
- runAsync(secondary.listPurchases(remoteNodeId))
- primary.listPurchases(remoteNodeId)
- }
-
- override def addPendingOnTheFlyFunding(remoteNodeId: PublicKey, pending: OnTheFlyFunding.Pending): Unit = {
- runAsync(secondary.addPendingOnTheFlyFunding(remoteNodeId, pending))
- primary.addPendingOnTheFlyFunding(remoteNodeId, pending)
- }
-
- override def removePendingOnTheFlyFunding(remoteNodeId: PublicKey, paymentHash: ByteVector32): Unit = {
- runAsync(secondary.removePendingOnTheFlyFunding(remoteNodeId, paymentHash))
- primary.removePendingOnTheFlyFunding(remoteNodeId, paymentHash)
- }
-
- override def listPendingOnTheFlyFunding(remoteNodeId: PublicKey): Map[ByteVector32, OnTheFlyFunding.Pending] = {
- runAsync(secondary.listPendingOnTheFlyFunding(remoteNodeId))
- primary.listPendingOnTheFlyFunding(remoteNodeId)
- }
-
- override def listPendingOnTheFlyFunding(): Map[PublicKey, Map[ByteVector32, OnTheFlyFunding.Pending]] = {
- runAsync(secondary.listPendingOnTheFlyFunding())
- primary.listPendingOnTheFlyFunding()
- }
-
- override def listPendingOnTheFlyPayments(): Map[PublicKey, Set[ByteVector32]] = {
- runAsync(secondary.listPendingOnTheFlyPayments())
- primary.listPendingOnTheFlyPayments()
- }
-
- override def addOnTheFlyFundingPreimage(preimage: ByteVector32): Unit = {
- runAsync(secondary.addOnTheFlyFundingPreimage(preimage))
- primary.addOnTheFlyFundingPreimage(preimage)
- }
-
- override def getOnTheFlyFundingPreimage(paymentHash: ByteVector32): Option[ByteVector32] = {
- runAsync(secondary.getOnTheFlyFundingPreimage(paymentHash))
- primary.getOnTheFlyFundingPreimage(paymentHash)
- }
-
- override def addFeeCredit(nodeId: PublicKey, amount: MilliSatoshi, receivedAt: TimestampMilli): MilliSatoshi = {
- runAsync(secondary.addFeeCredit(nodeId, amount, receivedAt))
- primary.addFeeCredit(nodeId, amount, receivedAt)
- }
-
- override def getFeeCredit(nodeId: PublicKey): MilliSatoshi = {
- runAsync(secondary.getFeeCredit(nodeId))
- primary.getFeeCredit(nodeId)
- }
-
- override def removeFeeCredit(nodeId: PublicKey, amountUsed: MilliSatoshi): MilliSatoshi = {
- runAsync(secondary.removeFeeCredit(nodeId, amountUsed))
- primary.removeFeeCredit(nodeId, amountUsed)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala
index 272981f..2232e3a 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala
@@ -79,6 +79,18 @@ trait JdbcUtils {
.headOption
}
+ /**
+ * We removed legacy channels codecs after the v0.13 eclair release, and migrated channels in that release.
+ * It is thus not possible to directly upgrade from an eclair version earlier than v0.13.
+ * We warn node operators that they must first run the v0.13 release to migrate their channel data.
+ */
+ def checkChannelsDbVersion(statement: Statement, db_name: String, minimum: Int): Unit = {
+ getVersion(statement, db_name) match {
+ case Some(v) if v < minimum => throw new IllegalArgumentException("You are updating from a version of eclair older than v0.13: please update to the v0.13 release first to migrate your channel data, and afterwards you'll be able to update to the latest version.")
+ case _ => ()
+ }
+ }
+
/**
* Updates the version for a particular logical database, it will overwrite the previous version.
*
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareAuditDb.scala
deleted file mode 100644
index 5d532e7..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareAuditDb.scala
+++ /dev/null
@@ -1,280 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.migration.CompareDb._
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object CompareAuditDb {
-
- private def compareSentTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "sent"
- val table2 = "audit.sent"
-
- def hash1(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- long(rs, "fees_msat") ++
- long(rs, "recipient_amount_msat") ++
- string(rs, "payment_id") ++
- string(rs, "parent_payment_id") ++
- bytes(rs, "payment_hash") ++
- bytes(rs, "payment_preimage") ++
- bytes(rs, "recipient_node_id") ++
- bytes(rs, "to_channel_id") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- long(rs, "fees_msat") ++
- long(rs, "recipient_amount_msat") ++
- string(rs, "payment_id") ++
- string(rs, "parent_payment_id") ++
- hex(rs, "payment_hash") ++
- hex(rs, "payment_preimage") ++
- hex(rs, "recipient_node_id") ++
- hex(rs, "to_channel_id") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareReceivedTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "received"
- val table2 = "audit.received"
-
- def hash1(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- bytes(rs, "payment_hash") ++
- bytes(rs, "from_channel_id") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- hex(rs, "payment_hash") ++
- hex(rs, "from_channel_id") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareRelayedTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "relayed"
- val table2 = "audit.relayed"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "payment_hash") ++
- long(rs, "amount_msat") ++
- bytes(rs, "channel_id") ++
- string(rs, "direction") ++
- string(rs, "relay_type") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "payment_hash") ++
- long(rs, "amount_msat") ++
- hex(rs, "channel_id") ++
- string(rs, "direction") ++
- string(rs, "relay_type") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareRelayedTrampolineTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "relayed_trampoline"
- val table2 = "audit.relayed_trampoline"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "payment_hash") ++
- long(rs, "amount_msat") ++
- bytes(rs, "next_node_id") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "payment_hash") ++
- long(rs, "amount_msat") ++
- hex(rs, "next_node_id") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareTransactionsPublishedTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "transactions_published"
- val table2 = "audit.transactions_published"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "tx_id") ++
- bytes(rs, "channel_id") ++
- bytes(rs, "node_id") ++
- long(rs, "mining_fee_sat") ++
- string(rs, "tx_type") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "tx_id") ++
- hex(rs, "channel_id") ++
- hex(rs, "node_id") ++
- long(rs, "mining_fee_sat") ++
- string(rs, "tx_type") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareTransactionsConfirmedTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "transactions_confirmed"
- val table2 = "audit.transactions_confirmed"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "tx_id") ++
- bytes(rs, "channel_id") ++
- bytes(rs, "node_id") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "tx_id") ++
- hex(rs, "channel_id") ++
- hex(rs, "node_id") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareChannelEventsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "channel_events"
- val table2 = "audit.channel_events"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "channel_id") ++
- bytes(rs, "node_id") ++
- long(rs, "capacity_sat") ++
- bool(rs, "is_funder") ++
- bool(rs, "is_private") ++
- string(rs, "event") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "channel_id") ++
- hex(rs, "node_id") ++
- long(rs, "capacity_sat") ++
- bool(rs, "is_funder") ++
- bool(rs, "is_private") ++
- string(rs, "event") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareChannelErrorsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "channel_errors WHERE error_name <> 'CannotAffordFees'"
- val table2 = "audit.channel_errors"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "channel_id") ++
- bytes(rs, "node_id") ++
- string(rs, "error_name") ++
- string(rs, "error_message") ++
- bool(rs, "is_fatal") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "channel_id") ++
- hex(rs, "node_id") ++
- string(rs, "error_name") ++
- string(rs, "error_message") ++
- bool(rs, "is_fatal") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareChannelUpdatesTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "channel_updates"
- val table2 = "audit.channel_updates"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "channel_id") ++
- bytes(rs, "node_id") ++
- long(rs, "fee_base_msat") ++
- long(rs, "fee_proportional_millionths") ++
- long(rs, "cltv_expiry_delta") ++
- long(rs, "htlc_minimum_msat") ++
- long(rs, "htlc_maximum_msat") ++
- longts(rs, "timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "channel_id") ++
- hex(rs, "node_id") ++
- long(rs, "fee_base_msat") ++
- long(rs, "fee_proportional_millionths") ++
- long(rs, "cltv_expiry_delta") ++
- long(rs, "htlc_minimum_msat") ++
- long(rs, "htlc_maximum_msat") ++
- ts(rs, "timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def comparePathFindingMetricsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "path_finding_metrics"
- val table2 = "audit.path_finding_metrics"
-
- def hash1(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- long(rs, "fees_msat") ++
- string(rs, "status") ++
- long(rs, "duration_ms") ++
- longts(rs, "timestamp") ++
- bool(rs, "is_mpp") ++
- string(rs, "experiment_name") ++
- bytes(rs, "recipient_node_id")
-
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- long(rs, "amount_msat") ++
- long(rs, "fees_msat") ++
- string(rs, "status") ++
- long(rs, "duration_ms") ++
- ts(rs, "timestamp") ++
- bool(rs, "is_mpp") ++
- string(rs, "experiment_name") ++
- hex(rs, "recipient_node_id")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- compareSentTable(conn1, conn2) &&
- compareReceivedTable(conn1, conn2) &&
- compareRelayedTable(conn1, conn2) &&
- compareRelayedTrampolineTable(conn1, conn2) &&
- compareTransactionsPublishedTable(conn1, conn2) &&
- compareTransactionsConfirmedTable(conn1, conn2) &&
- compareChannelEventsTable(conn1, conn2) &&
- compareChannelErrorsTable(conn1, conn2) &&
- compareChannelUpdatesTable(conn1, conn2) &&
- comparePathFindingMetricsTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareChannelsDb.scala
deleted file mode 100644
index 3905503..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareChannelsDb.scala
+++ /dev/null
@@ -1,80 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.BlockHeight
-import fr.acinq.eclair.channel.{DATA_CLOSING, DATA_WAIT_FOR_FUNDING_CONFIRMED}
-import fr.acinq.eclair.db.migration.CompareDb._
-import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object CompareChannelsDb {
-
- private def compareChannelsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "local_channels"
- val table2 = "local.channels"
-
- def hash1(rs: ResultSet): ByteVector = {
- val data = ByteVector(rs.getBytes("data"))
- val data_modified = channelDataCodec.decode(data.bits).require.value match {
- case c: DATA_WAIT_FOR_FUNDING_CONFIRMED => channelDataCodec.encode(c.copy(waitingSince = BlockHeight(0))).require.toByteVector
- case c: DATA_CLOSING => channelDataCodec.encode(c.copy(waitingSince = BlockHeight(0))).require.toByteVector
- case _ => data
- }
- bytes(rs, "channel_id") ++
- data_modified ++
- bool(rs, "is_closed") ++
- longtsnull(rs, "created_timestamp") ++
- longtsnull(rs, "last_payment_sent_timestamp") ++
- longtsnull(rs, "last_payment_received_timestamp") ++
- longtsnull(rs, "last_connected_timestamp") ++
- longtsnull(rs, "closed_timestamp")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- val data = ByteVector(rs.getBytes("data"))
- val data_modified = channelDataCodec.decode(data.bits).require.value match {
- case c: DATA_WAIT_FOR_FUNDING_CONFIRMED => channelDataCodec.encode(c.copy(waitingSince = BlockHeight(0))).require.toByteVector
- case c: DATA_CLOSING => channelDataCodec.encode(c.copy(waitingSince = BlockHeight(0))).require.toByteVector
- case _ => data
- }
- hex(rs, "channel_id") ++
- data_modified ++
- bool(rs, "is_closed") ++
- tsnull(rs, "created_timestamp") ++
- tsnull(rs, "last_payment_sent_timestamp") ++
- tsnull(rs, "last_payment_received_timestamp") ++
- tsnull(rs, "last_connected_timestamp") ++
- tsnull(rs, "closed_timestamp")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareHtlcInfosTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "htlc_infos"
- val table2 = "local.htlc_infos"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "channel_id") ++
- long(rs, "commitment_number") ++
- bytes(rs, "payment_hash") ++
- long(rs, "cltv_expiry")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "channel_id") ++
- long(rs, "commitment_number") ++
- hex(rs, "payment_hash") ++
- long(rs, "cltv_expiry")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- compareChannelsTable(conn1, conn2) &&
- compareHtlcInfosTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareDb.scala
deleted file mode 100644
index 7403b3b..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareDb.scala
+++ /dev/null
@@ -1,79 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.Databases.{PostgresDatabases, SqliteDatabases}
-import fr.acinq.eclair.db.DualDatabases
-import fr.acinq.eclair.db.jdbc.JdbcUtils.using
-import fr.acinq.eclair.db.pg.PgUtils
-import grizzled.slf4j.Logging
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object CompareDb extends Logging {
-
- def compareTable(conn1: Connection,
- conn2: Connection,
- table1: String,
- table2: String,
- hash1: ResultSet => ByteVector,
- hash2: ResultSet => ByteVector): Boolean = {
- var hashes1 = List.empty[ByteVector]
- using(conn1.prepareStatement(s"SELECT * FROM $table1")) { statement =>
- val rs = statement.executeQuery()
- while (rs.next()) hashes1 = hash1(rs) +: hashes1
- }
-
- var hashes2 = List.empty[ByteVector]
- using(conn2.prepareStatement(s"SELECT * FROM $table2")) { statement =>
- val rs = statement.executeQuery()
- while (rs.next()) hashes2 = hash2(rs) +: hashes2
- }
-
- if (hashes1.sorted == hashes2.sorted) {
- logger.info(s"tables $table1/$table2 are identical")
- true
- } else {
- val diff1 = hashes1 diff hashes2
- val diff2 = hashes2 diff hashes1
- logger.warn(s"tables $table1/$table2 are different diff1=${diff1.take(3).map(_.toHex.take(128))} diff2=${diff2.take(3).map(_.toHex.take(128))}")
- false
- }
- }
-
- // @formatter:off
- import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
- def bytes(rs: ResultSet, columnName: String): ByteVector = rs.getByteVector(columnName)
- def bytesnull(rs: ResultSet, columnName: String): ByteVector = rs.getByteVectorNullable(columnName).getOrElse(ByteVector.fromValidHex("deadbeef"))
- def hex(rs: ResultSet, columnName: String): ByteVector = rs.getByteVectorFromHex(columnName)
- def hexnull(rs: ResultSet, columnName: String): ByteVector = rs.getByteVectorFromHexNullable(columnName).getOrElse(ByteVector.fromValidHex("deadbeef"))
- def string(rs: ResultSet, columnName: String): ByteVector = ByteVector(rs.getString(columnName).getBytes)
- def stringnull(rs: ResultSet, columnName: String): ByteVector = ByteVector(rs.getStringNullable(columnName).getOrElse("<null>").getBytes)
- def bool(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromByte(if (rs.getBoolean(columnName)) 1 else 0)
- def long(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong(rs.getLong(columnName))
- def longnull(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong(rs.getLongNullable(columnName).getOrElse(42))
- def longts(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong((rs.getLong(columnName).toDouble / 1_000_000).round)
- def longtsnull(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong(rs.getLongNullable(columnName).map(l => (l.toDouble/1_000_000).round).getOrElse(42))
- def int(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromInt(rs.getInt(columnName))
- def ts(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong((rs.getTimestamp(columnName).getTime.toDouble / 1_000_000).round)
- def tsnull(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong(rs.getTimestampNullable(columnName).map(t => (t.getTime.toDouble / 1_000_000).round).getOrElse(42))
- def tssec(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong((rs.getTimestamp(columnName).toInstant.getEpochSecond.toDouble / 1_000_000).round)
- def tssecnull(rs: ResultSet, columnName: String): ByteVector = ByteVector.fromLong(rs.getTimestampNullable(columnName).map(t => (t.toInstant.getEpochSecond.toDouble / 1_000_000).round).getOrElse(42))
- // @formatter:on
-
- def compareAll(dualDatabases: DualDatabases): Unit = {
- logger.info("comparing all tables...")
- val (sqliteDb: SqliteDatabases, postgresDb: PostgresDatabases) = DualDatabases.getDatabases(dualDatabases)
- PgUtils.inTransaction { postgres =>
- val result = List(
- CompareChannelsDb.compareAllTables(sqliteDb.channels.sqlite, postgres),
- ComparePendingCommandsDb.compareAllTables(sqliteDb.pendingCommands.sqlite, postgres),
- ComparePeersDb.compareAllTables(sqliteDb.peers.sqlite, postgres),
- ComparePaymentsDb.compareAllTables(sqliteDb.payments.sqlite, postgres),
- CompareNetworkDb.compareAllTables(sqliteDb.network.sqlite, postgres),
- CompareAuditDb.compareAllTables(sqliteDb.audit.sqlite, postgres)
- ).forall(_ == true)
- logger.info(s"comparison complete identical=$result")
- }(postgresDb.dataSource)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareNetworkDb.scala
deleted file mode 100644
index 8b91bb3..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/CompareNetworkDb.scala
+++ /dev/null
@@ -1,73 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.migration.CompareDb._
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object CompareNetworkDb {
-
- private def compareNodesTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "nodes"
- val table2 = "network.nodes"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "node_id") ++
- bytes(rs, "data")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "node_id") ++
- bytes(rs, "data")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareChannelsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "channels"
- val table2 = "network.public_channels"
-
- def hash1(rs: ResultSet): ByteVector = {
- long(rs, "short_channel_id") ++
- string(rs, "txid") ++
- bytes(rs, "channel_announcement") ++
- long(rs, "capacity_sat") ++
- bytesnull(rs, "channel_update_1") ++
- bytesnull(rs, "channel_update_2")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- long(rs, "short_channel_id") ++
- string(rs, "txid") ++
- bytes(rs, "channel_announcement") ++
- long(rs, "capacity_sat") ++
- bytesnull(rs, "channel_update_1") ++
- bytesnull(rs, "channel_update_2")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def comparePrunedTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "pruned"
- val table2 = "network.pruned_channels"
-
- def hash1(rs: ResultSet): ByteVector = {
- long(rs, "short_channel_id")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- long(rs, "short_channel_id")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- compareNodesTable(conn1, conn2) &&
- compareChannelsTable(conn1, conn2) &&
- comparePrunedTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePaymentsDb.scala
deleted file mode 100644
index 1726593..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePaymentsDb.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.migration.CompareDb._
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object ComparePaymentsDb {
-
- private def compareReceivedPaymentsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "received_payments"
- val table2 = "payments.received"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "payment_hash") ++
- string(rs, "payment_type") ++
- bytes(rs, "payment_preimage") ++
- string(rs, "payment_request") ++
- longnull(rs, "received_msat") ++
- longts(rs, "created_at") ++
- longts(rs, "expire_at") ++
- longtsnull(rs, "received_at")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "payment_hash") ++
- string(rs, "payment_type") ++
- hex(rs, "payment_preimage") ++
- string(rs, "payment_request") ++
- longnull(rs, "received_msat") ++
- ts(rs, "created_at") ++
- ts(rs, "expire_at") ++
- tsnull(rs, "received_at")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareSentPaymentsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "sent_payments"
- val table2 = "payments.sent"
-
- def hash1(rs: ResultSet): ByteVector = {
- string(rs, "id") ++
- string(rs, "parent_id") ++
- stringnull(rs, "external_id") ++
- bytes(rs, "payment_hash") ++
- bytesnull(rs, "payment_preimage") ++
- string(rs, "payment_type") ++
- long(rs, "amount_msat") ++
- longnull(rs, "fees_msat") ++
- long(rs, "recipient_amount_msat") ++
- bytes(rs, "recipient_node_id") ++
- stringnull(rs, "payment_request") ++
- bytesnull(rs, "payment_route") ++
- bytesnull(rs, "failures") ++
- longts(rs, "created_at") ++
- longtsnull(rs, "completed_at")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- string(rs, "id") ++
- string(rs, "parent_id") ++
- stringnull(rs, "external_id") ++
- hex(rs, "payment_hash") ++
- hexnull(rs, "payment_preimage") ++
- string(rs, "payment_type") ++
- long(rs, "amount_msat") ++
- longnull(rs, "fees_msat") ++
- long(rs, "recipient_amount_msat") ++
- hex(rs, "recipient_node_id") ++
- stringnull(rs, "payment_request") ++
- bytesnull(rs, "payment_route") ++
- bytesnull(rs, "failures") ++
- ts(rs, "created_at") ++
- tsnull(rs, "completed_at")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- compareReceivedPaymentsTable(conn1, conn2) &&
- compareSentPaymentsTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePeersDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePeersDb.scala
deleted file mode 100644
index c0c034f..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePeersDb.scala
+++ /dev/null
@@ -1,51 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.migration.CompareDb._
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object ComparePeersDb {
-
- private def comparePeersTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "peers"
- val table2 = "local.peers"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "node_id") ++
- bytes(rs, "data")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "node_id") ++
- bytes(rs, "data")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- private def compareRelayFeesTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "relay_fees"
- val table2 = "local.relay_fees"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "node_id") ++
- long(rs, "fee_base_msat") ++
- long(rs, "fee_proportional_millionths")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "node_id") ++
- long(rs, "fee_base_msat") ++
- long(rs, "fee_proportional_millionths")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- comparePeersTable(conn1, conn2) &&
- compareRelayFeesTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePendingCommandsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePendingCommandsDb.scala
deleted file mode 100644
index eb099ce..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/ComparePendingCommandsDb.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.migration.CompareDb._
-import scodec.bits.ByteVector
-
-import java.sql.{Connection, ResultSet}
-
-object ComparePendingCommandsDb {
-
- private def comparePendingSettlementCommandsTable(conn1: Connection, conn2: Connection): Boolean = {
- val table1 = "pending_settlement_commands"
- val table2 = "local.pending_settlement_commands"
-
- def hash1(rs: ResultSet): ByteVector = {
- bytes(rs, "channel_id") ++
- long(rs, "htlc_id") ++
- bytes(rs, "data")
- }
-
- def hash2(rs: ResultSet): ByteVector = {
- hex(rs, "channel_id") ++
- long(rs, "htlc_id") ++
- bytes(rs, "data")
- }
-
- compareTable(conn1, conn2, table1, table2, hash1, hash2)
- }
-
- def compareAllTables(conn1: Connection, conn2: Connection): Boolean = {
- comparePendingSettlementCommandsTable(conn1, conn2)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateAuditDb.scala
deleted file mode 100644
index 28705cd..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateAuditDb.scala
+++ /dev/null
@@ -1,188 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-
-import java.sql.{Connection, PreparedStatement, ResultSet, Timestamp}
-import java.time.Instant
-
-object MigrateAuditDb {
-
- private def migrateSentTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "sent"
- val insertSql = "INSERT INTO audit.sent (amount_msat, fees_msat, recipient_amount_msat, payment_id, parent_payment_id, payment_hash, payment_preimage, recipient_node_id, to_channel_id, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setLong(1, rs.getLong("amount_msat"))
- insertStatement.setLong(2, rs.getLong("fees_msat"))
- insertStatement.setLong(3, rs.getLong("recipient_amount_msat"))
- insertStatement.setString(4, rs.getString("payment_id"))
- insertStatement.setString(5, rs.getString("parent_payment_id"))
- insertStatement.setString(6, rs.getByteVector32("payment_hash").toHex)
- insertStatement.setString(7, rs.getByteVector32("payment_preimage").toHex)
- insertStatement.setString(8, rs.getByteVector("recipient_node_id").toHex)
- insertStatement.setString(9, rs.getByteVector32("to_channel_id").toHex)
- insertStatement.setTimestamp(10, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateReceivedTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "received"
- val insertSql = "INSERT INTO audit.received (amount_msat, payment_hash, from_channel_id, timestamp) VALUES (?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setLong(1, rs.getLong("amount_msat"))
- insertStatement.setString(2, rs.getByteVector32("payment_hash").toHex)
- insertStatement.setString(3, rs.getByteVector32("from_channel_id").toHex)
- insertStatement.setTimestamp(4, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateRelayedTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "relayed"
- val insertSql = "INSERT INTO audit.relayed (payment_hash, amount_msat, channel_id, direction, relay_type, timestamp) VALUES (?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("payment_hash").toHex)
- insertStatement.setLong(2, rs.getLong("amount_msat"))
- insertStatement.setString(3, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(4, rs.getString("direction"))
- insertStatement.setString(5, rs.getString("relay_type"))
- insertStatement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateRelayedTrampolineTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "relayed_trampoline"
- val insertSql = "INSERT INTO audit.relayed_trampoline (payment_hash, amount_msat, next_node_id, timestamp) VALUES (?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("payment_hash").toHex)
- insertStatement.setLong(2, rs.getLong("amount_msat"))
- insertStatement.setString(3, rs.getByteVector("next_node_id").toHex)
- insertStatement.setTimestamp(4, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateTransactionsPublishedTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "transactions_published"
- val insertSql = "INSERT INTO audit.transactions_published (tx_id, channel_id, node_id, mining_fee_sat, tx_type, timestamp) VALUES (?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("tx_id").toHex)
- insertStatement.setString(2, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(3, rs.getByteVector("node_id").toHex)
- insertStatement.setLong(4, rs.getLong("mining_fee_sat"))
- insertStatement.setString(5, rs.getString("tx_type"))
- insertStatement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateTransactionsConfirmedTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "transactions_confirmed"
- val insertSql = "INSERT INTO audit.transactions_confirmed (tx_id, channel_id, node_id, timestamp) VALUES (?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("tx_id").toHex)
- insertStatement.setString(2, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(3, rs.getByteVector("node_id").toHex)
- insertStatement.setTimestamp(4, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateChannelEventsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "channel_events"
- val insertSql = "INSERT INTO audit.channel_events (channel_id, node_id, capacity_sat, is_funder, is_private, event, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(2, rs.getByteVector("node_id").toHex)
- insertStatement.setLong(3, rs.getLong("capacity_sat"))
- insertStatement.setBoolean(4, rs.getBoolean("is_funder"))
- insertStatement.setBoolean(5, rs.getBoolean("is_private"))
- insertStatement.setString(6, rs.getString("event"))
- insertStatement.setTimestamp(7, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateChannelErrorsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "channel_errors WHERE error_name <> 'CannotAffordFees'"
- val insertSql = "INSERT INTO audit.channel_errors (channel_id, node_id, error_name, error_message, is_fatal, timestamp) VALUES (?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(2, rs.getByteVector("node_id").toHex)
- insertStatement.setString(3, rs.getString("error_name"))
- insertStatement.setString(4, rs.getString("error_message"))
- insertStatement.setBoolean(5, rs.getBoolean("is_fatal"))
- insertStatement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateChannelUpdatesTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "channel_updates"
- val insertSql = "INSERT INTO audit.channel_updates (channel_id, node_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta, htlc_minimum_msat, htlc_maximum_msat, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("channel_id").toHex)
- insertStatement.setString(2, rs.getByteVector("node_id").toHex)
- insertStatement.setLong(3, rs.getLong("fee_base_msat"))
- insertStatement.setLong(4, rs.getLong("fee_proportional_millionths"))
- insertStatement.setLong(5, rs.getLong("cltv_expiry_delta"))
- insertStatement.setLong(6, rs.getLong("htlc_minimum_msat"))
- insertStatement.setLong(7, rs.getLong("htlc_maximum_msat"))
- insertStatement.setTimestamp(8, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migratePathFindingMetricsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "path_finding_metrics"
- val insertSql = "INSERT INTO audit.path_finding_metrics (amount_msat, fees_msat, status, duration_ms, timestamp, is_mpp, experiment_name, recipient_node_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setLong(1, rs.getLong("amount_msat"))
- insertStatement.setLong(2, rs.getLong("fees_msat"))
- insertStatement.setString(3, rs.getString("status"))
- insertStatement.setLong(4, rs.getLong("duration_ms"))
- insertStatement.setTimestamp(5, Timestamp.from(Instant.ofEpochMilli(rs.getLong("timestamp"))))
- insertStatement.setBoolean(6, rs.getBoolean("is_mpp"))
- insertStatement.setString(7, rs.getString("experiment_name"))
- insertStatement.setString(8, rs.getByteVector("recipient_node_id").toHex)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "audit", 8, 10)
- migrateSentTable(source, destination)
- migrateReceivedTable(source, destination)
- migrateRelayedTable(source, destination)
- migrateRelayedTrampolineTable(source, destination)
- migrateTransactionsPublishedTable(source, destination)
- migrateTransactionsConfirmedTable(source, destination)
- migrateChannelEventsTable(source, destination)
- migrateChannelErrorsTable(source, destination)
- migrateChannelUpdatesTable(source, destination)
- migratePathFindingMetricsTable(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateChannelsDb.scala
deleted file mode 100644
index 2b4165d..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateChannelsDb.scala
+++ /dev/null
@@ -1,56 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec
-import scodec.bits.BitVector
-
-import java.sql.{Connection, PreparedStatement, ResultSet, Timestamp}
-import java.time.Instant
-
-object MigrateChannelsDb {
-
- private def migrateChannelsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "local_channels"
- val insertSql = "INSERT INTO local.channels (channel_id, data, json, is_closed, created_timestamp, last_payment_sent_timestamp, last_payment_received_timestamp, last_connected_timestamp, closed_timestamp) VALUES (?, ?, ?::JSONB, ?, ?, ?, ?, ?, ?)"
-
- import fr.acinq.eclair.json.JsonSerializers._
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("channel_id").toHex)
- insertStatement.setBytes(2, rs.getBytes("data"))
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val json = serialization.write(state)
- insertStatement.setString(3, json)
- insertStatement.setBoolean(4, rs.getBoolean("is_closed"))
- insertStatement.setTimestamp(5, rs.getLongNullable("created_timestamp").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- insertStatement.setTimestamp(6, rs.getLongNullable("last_payment_sent_timestamp").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- insertStatement.setTimestamp(7, rs.getLongNullable("last_payment_received_timestamp").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- insertStatement.setTimestamp(8, rs.getLongNullable("last_connected_timestamp").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- insertStatement.setTimestamp(9, rs.getLongNullable("closed_timestamp").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateHtlcInfos(source: Connection, destination: Connection): Int = {
- val sourceTable = "htlc_infos"
- val insertSql = "INSERT INTO local.htlc_infos (channel_id, commitment_number, payment_hash, cltv_expiry) VALUES (?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector32("channel_id").toHex)
- insertStatement.setLong(2, rs.getLong("commitment_number"))
- insertStatement.setString(3, rs.getByteVector32("payment_hash").toHex)
- insertStatement.setLong(4, rs.getLong("cltv_expiry"))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "channels", 4, 7)
- migrateChannelsTable(source, destination)
- migrateHtlcInfos(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateDb.scala
deleted file mode 100644
index 28d776a..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateDb.scala
+++ /dev/null
@@ -1,54 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.Databases.{PostgresDatabases, SqliteDatabases}
-import fr.acinq.eclair.db.DualDatabases
-import fr.acinq.eclair.db.jdbc.JdbcUtils
-import fr.acinq.eclair.db.jdbc.JdbcUtils.using
-import fr.acinq.eclair.db.pg.PgUtils
-import grizzled.slf4j.Logging
-
-import java.sql.{Connection, PreparedStatement, ResultSet}
-
-object MigrateDb extends Logging {
-
- private def getVersion(conn: Connection, dbName: String): Int = {
- using(conn.prepareStatement(s"SELECT version FROM versions WHERE db_name='$dbName'")) { statement =>
- val res = statement.executeQuery()
- res.next()
- res.getInt("version")
- }
- }
-
- def checkVersions(source: Connection,
- destination: Connection,
- dbName: String,
- expectedSourceVersion: Int,
- expectedDestinationVersion: Int): Unit = {
- val actualSourceVersion = getVersion(source, dbName)
- val actualDestinationVersion = getVersion(destination, dbName)
- require(actualSourceVersion == expectedSourceVersion, s"unexpected version for source db=$dbName expected=$expectedSourceVersion actual=$actualSourceVersion")
- require(actualDestinationVersion == expectedDestinationVersion, s"unexpected version for destination db=$dbName expected=$expectedDestinationVersion actual=$actualDestinationVersion")
- }
-
- def migrateTable(source: Connection,
- destination: Connection,
- sourceTable: String,
- insertSql: String,
- migrate: (ResultSet, PreparedStatement) => Unit): Int =
- JdbcUtils.migrateTable(source, destination, sourceTable, insertSql, migrate)(logger)
-
- def migrateAll(dualDatabases: DualDatabases): Unit = {
- logger.info("migrating all tables...")
- val (sqliteDb: SqliteDatabases, postgresDb: PostgresDatabases) = DualDatabases.getDatabases(dualDatabases)
- PgUtils.inTransaction { postgres =>
- MigrateChannelsDb.migrateAllTables(sqliteDb.channels.sqlite, postgres)
- MigratePendingCommandsDb.migrateAllTables(sqliteDb.pendingCommands.sqlite, postgres)
- MigratePeersDb.migrateAllTables(sqliteDb.peers.sqlite, postgres)
- MigratePaymentsDb.migrateAllTables(sqliteDb.payments.sqlite, postgres)
- MigrateNetworkDb.migrateAllTables(sqliteDb.network.sqlite, postgres)
- MigrateAuditDb.migrateAllTables(sqliteDb.audit.sqlite, postgres)
- logger.info("migration complete")
- }(postgresDb.dataSource)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateNetworkDb.scala
deleted file mode 100644
index 0f2b4cc..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigrateNetworkDb.scala
+++ /dev/null
@@ -1,74 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, nodeAnnouncementCodec}
-import scodec.bits.BitVector
-
-import java.sql.{Connection, PreparedStatement, ResultSet}
-
-object MigrateNetworkDb {
-
- private def migrateNodesTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "nodes"
- val insertSql = "INSERT INTO network.nodes (node_id, data, json) VALUES (?, ?, ?::JSONB)"
-
- import fr.acinq.eclair.json.JsonSerializers._
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector("node_id").toHex)
- insertStatement.setBytes(2, rs.getBytes("data"))
- val state = nodeAnnouncementCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val json = serialization.write(state)
- insertStatement.setString(3, json)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateChannelsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "channels"
- val insertSql = "INSERT INTO network.public_channels (short_channel_id, txid, channel_announcement, capacity_sat, channel_update_1, channel_update_2, channel_announcement_json, channel_update_1_json, channel_update_2_json) VALUES (?, ?, ?, ?, ?, ?, ?::JSONB, ?::JSONB, ?::JSONB)"
-
- import fr.acinq.eclair.json.JsonSerializers._
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setLong(1, rs.getLong("short_channel_id"))
- insertStatement.setString(2, rs.getString("txid"))
- insertStatement.setBytes(3, rs.getBytes("channel_announcement"))
- insertStatement.setLong(4, rs.getLong("capacity_sat"))
- insertStatement.setBytes(5, rs.getBytes("channel_update_1"))
- insertStatement.setBytes(6, rs.getBytes("channel_update_2"))
- val ann = channelAnnouncementCodec.decode(rs.getBitVectorOpt("channel_announcement").get).require.value
- val channel_update_1_opt = rs.getBitVectorOpt("channel_update_1").map(channelUpdateCodec.decode(_).require.value)
- val channel_update_2_opt = rs.getBitVectorOpt("channel_update_2").map(channelUpdateCodec.decode(_).require.value)
- val json = serialization.write(ann)
- val u1_json = channel_update_1_opt.map(serialization.write(_)).orNull
- val u2_json = channel_update_2_opt.map(serialization.write(_)).orNull
- insertStatement.setString(7, json)
- insertStatement.setString(8, u1_json)
- insertStatement.setString(9, u2_json)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migratePrunedTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "pruned"
- val insertSql = "INSERT INTO network.pruned_channels (short_channel_id) VALUES (?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setLong(1, rs.getLong("short_channel_id"))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "network", 2, 4)
- migrateNodesTable(source, destination)
- migrateChannelsTable(source, destination)
- migratePrunedTable(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePaymentsDb.scala
deleted file mode 100644
index ac2e885..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePaymentsDb.scala
+++ /dev/null
@@ -1,60 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-
-import java.sql.{Connection, PreparedStatement, ResultSet, Timestamp}
-import java.time.Instant
-
-object MigratePaymentsDb {
-
- private def migrateReceivedPaymentsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "received_payments"
- val insertSql = "INSERT INTO payments.received (payment_hash, payment_type, payment_preimage, payment_request, received_msat, created_at, expire_at, received_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector("payment_hash").toHex)
- insertStatement.setString(2, rs.getString("payment_type"))
- insertStatement.setString(3, rs.getByteVector("payment_preimage").toHex)
- insertStatement.setString(4, rs.getString("payment_request"))
- insertStatement.setObject(5, rs.getLongNullable("received_msat").orNull)
- insertStatement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(rs.getLong("created_at"))))
- insertStatement.setTimestamp(7, Timestamp.from(Instant.ofEpochMilli(rs.getLong("expire_at"))))
- insertStatement.setObject(8, rs.getLongNullable("received_at").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateSentPaymentsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "sent_payments"
- val insertSql = "INSERT INTO payments.sent (id, parent_id, external_id, payment_hash, payment_preimage, payment_type, amount_msat, fees_msat, recipient_amount_msat, recipient_node_id, payment_request, payment_route, failures, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getString("id"))
- insertStatement.setString(2, rs.getString("parent_id"))
- insertStatement.setString(3, rs.getStringNullable("external_id").orNull)
- insertStatement.setString(4, rs.getByteVector("payment_hash").toHex)
- insertStatement.setString(5, rs.getByteVector32Nullable("payment_preimage").map(_.toHex).orNull)
- insertStatement.setString(6, rs.getString("payment_type"))
- insertStatement.setLong(7, rs.getLong("amount_msat"))
- insertStatement.setObject(8, rs.getLongNullable("fees_msat").orNull)
- insertStatement.setLong(9, rs.getLong("recipient_amount_msat"))
- insertStatement.setString(10, rs.getByteVector("recipient_node_id").toHex)
- insertStatement.setString(11, rs.getStringNullable("payment_request").orNull)
- insertStatement.setBytes(12, rs.getBytes("payment_route"))
- insertStatement.setBytes(13, rs.getBytes("failures"))
- insertStatement.setTimestamp(14, Timestamp.from(Instant.ofEpochMilli(rs.getLong("created_at"))))
- insertStatement.setObject(15, rs.getLongNullable("completed_at").map(l => Timestamp.from(Instant.ofEpochMilli(l))).orNull)
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "payments", 4, 6)
- migrateReceivedPaymentsTable(source, destination)
- migrateSentPaymentsTable(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePeersDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePeersDb.scala
deleted file mode 100644
index 77021f3..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePeersDb.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-
-import java.sql.{Connection, PreparedStatement, ResultSet}
-
-object MigratePeersDb {
-
- private def migratePeersTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "peers"
- val insertSql = "INSERT INTO local.peers (node_id, data) VALUES (?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector("node_id").toHex)
- insertStatement.setBytes(2, rs.getBytes("data"))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- private def migrateRelayFeesTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "relay_fees"
- val insertSql = "INSERT INTO local.relay_fees (node_id, fee_base_msat, fee_proportional_millionths) VALUES (?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector("node_id").toHex)
- insertStatement.setLong(2, rs.getLong("fee_base_msat"))
- insertStatement.setLong(3, rs.getLong("fee_proportional_millionths"))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "peers", 2, 3)
- migratePeersTable(source, destination)
- migrateRelayFeesTable(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePendingCommandsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePendingCommandsDb.scala
deleted file mode 100644
index 6a01208..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/migration/MigratePendingCommandsDb.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-package fr.acinq.eclair.db.migration
-
-import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
-import fr.acinq.eclair.db.migration.MigrateDb.{checkVersions, migrateTable}
-
-import java.sql.{Connection, PreparedStatement, ResultSet}
-
-object MigratePendingCommandsDb {
-
- private def migratePendingSettlementCommandsTable(source: Connection, destination: Connection): Int = {
- val sourceTable = "pending_settlement_commands"
- val insertSql = "INSERT INTO local.pending_settlement_commands (channel_id, htlc_id, data) VALUES (?, ?, ?)"
-
- def migrate(rs: ResultSet, insertStatement: PreparedStatement): Unit = {
- insertStatement.setString(1, rs.getByteVector("channel_id").toHex)
- insertStatement.setLong(2, rs.getLong("htlc_id"))
- insertStatement.setBytes(3, rs.getBytes("data"))
- }
-
- migrateTable(source, destination, sourceTable, insertSql, migrate)
- }
-
- def migrateAllTables(source: Connection, destination: Connection): Unit = {
- checkVersions(source, destination, "pending_relay", 2, 3)
- migratePendingSettlementCommandsTable(source, destination)
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scala
index 103f382..50d533e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scala
@@ -30,7 +30,7 @@ import fr.acinq.eclair.{CltvExpiry, Paginated}
import grizzled.slf4j.Logging
import scodec.bits.BitVector
-import java.sql.{Connection, Statement, Timestamp}
+import java.sql.{Connection, Timestamp}
import java.time.Instant
import javax.sql.DataSource
@@ -49,100 +49,6 @@ class PgChannelsDb(implicit ds: DataSource, lock: PgLock) extends ChannelsDb wit
inTransaction { pg =>
using(pg.createStatement()) { statement =>
-
- def migration23(statement: Statement): Unit = {
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN created_timestamp BIGINT")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_payment_sent_timestamp BIGINT")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_payment_received_timestamp BIGINT")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_connected_timestamp BIGINT")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN closed_timestamp BIGINT")
- }
-
- def migration34(statement: Statement): Unit = {
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN created_timestamp SET DATA TYPE TIMESTAMP WITH TIME ZONE USING timestamp with time zone 'epoch' + created_timestamp * interval '1 millisecond'")
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN last_payment_sent_timestamp SET DATA TYPE TIMESTAMP WITH TIME ZONE USING timestamp with time zone 'epoch' + last_payment_sent_timestamp * interval '1 millisecond'")
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN last_payment_received_timestamp SET DATA TYPE TIMESTAMP WITH TIME ZONE USING timestamp with time zone 'epoch' + last_payment_received_timestamp * interval '1 millisecond'")
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN last_connected_timestamp SET DATA TYPE TIMESTAMP WITH TIME ZONE USING timestamp with time zone 'epoch' + last_connected_timestamp * interval '1 millisecond'")
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN closed_timestamp SET DATA TYPE TIMESTAMP WITH TIME ZONE USING timestamp with time zone 'epoch' + closed_timestamp * interval '1 millisecond'")
-
- statement.executeUpdate("ALTER TABLE htlc_infos ALTER COLUMN commitment_number SET DATA TYPE BIGINT USING commitment_number::BIGINT")
- }
-
- def migration45(statement: Statement): Unit = {
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN json JSONB")
- resetJsonColumns(pg, oldTableName = true)
- statement.executeUpdate("ALTER TABLE local_channels ALTER COLUMN json SET NOT NULL")
- statement.executeUpdate("CREATE INDEX local_channels_type_idx ON local_channels ((json->>'type'))")
- statement.executeUpdate("CREATE INDEX local_channels_remote_node_id_idx ON local_channels ((json->'commitments'->'params'->'remoteParams'->>'nodeId'))")
- }
-
- def migration56(statement: Statement): Unit = {
- statement.executeUpdate("CREATE SCHEMA IF NOT EXISTS local")
- statement.executeUpdate("ALTER TABLE local_channels SET SCHEMA local")
- statement.executeUpdate("ALTER TABLE local.local_channels RENAME TO channels")
- statement.executeUpdate("ALTER TABLE htlc_infos SET SCHEMA local")
- }
-
- def migration67(): Unit = {
- migrateTable(pg, pg,
- "local.channels",
- "UPDATE local.channels SET data=?, json=?::JSONB WHERE channel_id=?",
- (rs, statement) => {
- // This forces a re-serialization of the channel data with latest codecs, because as of codecs v3 we don't
- // store local commitment signatures anymore, and we want to clean up existing data
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val data = channelDataCodec.encode(state).require.toByteArray
- val json = serialization.write(state)
- statement.setBytes(1, data)
- statement.setString(2, json)
- statement.setString(3, state.channelId.toHex)
- }
- )(logger)
- }
-
- def migration78(statement: Statement): Unit = {
- statement.executeUpdate("DROP INDEX IF EXISTS local.local_channels_remote_node_id_idx")
- statement.executeUpdate("ALTER TABLE local.channels ADD COLUMN remote_node_id TEXT")
- migrateTable(pg, pg,
- "local.channels",
- "UPDATE local.channels SET remote_node_id=? WHERE channel_id=?",
- (rs, statement) => {
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- statement.setString(1, state.remoteNodeId.toHex)
- statement.setString(2, state.channelId.toHex)
- })(logger)
- statement.executeUpdate("ALTER TABLE local.channels ALTER COLUMN remote_node_id SET NOT NULL")
- statement.executeUpdate("CREATE INDEX local_channels_remote_node_id_idx ON local.channels(remote_node_id)")
- }
-
- def migration89(statement: Statement): Unit = {
- statement.executeUpdate("CREATE TABLE local.htlc_infos_to_remove (channel_id TEXT NOT NULL PRIMARY KEY, before_commitment_number BIGINT NOT NULL)")
- }
-
- def migration910(statement: Statement): Unit = {
- // We're changing our composite index to two distinct indices to improve performance.
- statement.executeUpdate("CREATE INDEX htlc_infos_channel_id_idx ON local.htlc_infos(channel_id)")
- statement.executeUpdate("CREATE INDEX htlc_infos_commitment_number_idx ON local.htlc_infos(commitment_number)")
- statement.executeUpdate("DROP INDEX IF EXISTS local.htlc_infos_idx")
- }
-
- def migration1011(statement: Statement): Unit = {
- migrateTable(pg, pg,
- "local.channels",
- "UPDATE local.channels SET data=?, json=?::JSONB WHERE channel_id=?",
- (rs, statement) => {
- // This forces a re-serialization of the channel data with latest codecs, because we want to remove support
- // for codecs older than v5 in the next release.
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val data = channelDataCodec.encode(state).require.toByteArray
- val json = serialization.write(state)
- statement.setBytes(1, data)
- statement.setString(2, json)
- statement.setString(3, state.channelId.toHex)
- }
- )(logger)
- }
-
getVersion(statement, DB_NAME) match {
case None =>
statement.executeUpdate("CREATE SCHEMA IF NOT EXISTS local")
@@ -157,35 +63,7 @@ class PgChannelsDb(implicit ds: DataSource, lock: PgLock) extends ChannelsDb wit
// This is more efficient because we're writing a lot to this table but only reading when a channel is force-closed.
statement.executeUpdate("CREATE INDEX htlc_infos_channel_id_idx ON local.htlc_infos(channel_id)")
statement.executeUpdate("CREATE INDEX htlc_infos_commitment_number_idx ON local.htlc_infos(commitment_number)")
- case Some(v@(2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)) =>
- logger.warn(s"migrating db $DB_NAME, found version=$v current=$CURRENT_VERSION")
- if (v < 3) {
- migration23(statement)
- }
- if (v < 4) {
- migration34(statement)
- }
- if (v < 5) {
- migration45(statement)
- }
- if (v < 6) {
- migration56(statement)
- }
- if (v < 7) {
- migration67()
- }
- if (v < 8) {
- migration78(statement)
- }
- if (v < 9) {
- migration89(statement)
- }
- if (v < 10) {
- migration910(statement)
- }
- if (v < 11) {
- migration1011(statement)
- }
+ case Some(v) if v < 11 => throw new RuntimeException("You are updating from a version of eclair older than v0.13: please update to the v0.13 release first to migrate your channel data, and afterwards you'll be able to update to the latest version.")
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala
index 4a27b40..bf6ead0 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala
@@ -26,9 +26,8 @@ import fr.acinq.eclair.db.Monitoring.Tags.DbBackends
import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec
import fr.acinq.eclair.{CltvExpiry, Paginated, TimestampMilli}
import grizzled.slf4j.Logging
-import scodec.bits.BitVector
-import java.sql.{Connection, Statement}
+import java.sql.Connection
object SqliteChannelsDb {
val DB_NAME = "channels"
@@ -51,60 +50,6 @@ class SqliteChannelsDb(val sqlite: Connection) extends ChannelsDb with Logging {
}
using(sqlite.createStatement(), inTransaction = true) { statement =>
-
- def migration12(statement: Statement): Unit = {
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN is_closed BOOLEAN NOT NULL DEFAULT 0")
- }
-
- def migration23(statement: Statement): Unit = {
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN created_timestamp INTEGER")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_payment_sent_timestamp INTEGER")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_payment_received_timestamp INTEGER")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN last_connected_timestamp INTEGER")
- statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN closed_timestamp INTEGER")
- }
-
- def migration34(): Unit = {
- migrateTable(sqlite, sqlite,
- "local_channels",
- s"UPDATE local_channels SET data=? WHERE channel_id=?",
- (rs, statement) => {
- // This forces a re-serialization of the channel data with latest codecs, because as of codecs v3 we don't
- // store local commitment signatures anymore, and we want to clean up existing data
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val data = channelDataCodec.encode(state).require.toByteArray
- statement.setBytes(1, data)
- statement.setBytes(2, state.channelId.toArray)
- }
- )(logger)
- }
-
- def migration45(): Unit = {
- statement.executeUpdate("CREATE TABLE htlc_infos_to_remove (channel_id BLOB NOT NULL PRIMARY KEY, before_commitment_number INTEGER NOT NULL)")
- }
-
- def migration56(): Unit = {
- // We're changing our composite index to two distinct indices to improve performance.
- statement.executeUpdate("CREATE INDEX htlc_infos_channel_id_idx ON htlc_infos(channel_id)")
- statement.executeUpdate("CREATE INDEX htlc_infos_commitment_number_idx ON htlc_infos(commitment_number)")
- statement.executeUpdate("DROP INDEX IF EXISTS htlc_infos_idx")
- }
-
- def migration67(): Unit = {
- migrateTable(sqlite, sqlite,
- "local_channels",
- "UPDATE local_channels SET data=? WHERE channel_id=?",
- (rs, statement) => {
- // This forces a re-serialization of the channel data with latest codecs, because we want to remove support
- // for codecs older than v5 in the next release.
- val state = channelDataCodec.decode(BitVector(rs.getBytes("data"))).require.value
- val data = channelDataCodec.encode(state).require.toByteArray
- statement.setBytes(1, data)
- statement.setBytes(2, state.channelId.toArray)
- }
- )(logger)
- }
-
getVersion(statement, DB_NAME) match {
case None =>
statement.executeUpdate("CREATE TABLE local_channels (channel_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL, is_closed BOOLEAN NOT NULL DEFAULT 0, created_timestamp INTEGER, last_payment_sent_timestamp INTEGER, last_payment_received_timestamp INTEGER, last_connected_timestamp INTEGER, closed_timestamp INTEGER)")
@@ -114,26 +59,7 @@ class SqliteChannelsDb(val sqlite: Connection) extends ChannelsDb with Logging {
// This is more efficient because we're writing a lot to this table but only reading when a channel is force-closed.
statement.executeUpdate("CREATE INDEX htlc_infos_channel_id_idx ON htlc_infos(channel_id)")
statement.executeUpdate("CREATE INDEX htlc_infos_commitment_number_idx ON htlc_infos(commitment_number)")
- case Some(v@(1 | 2 | 3 | 4 | 5 | 6)) =>
- logger.warn(s"migrating db $DB_NAME, found version=$v current=$CURRENT_VERSION")
- if (v < 2) {
- migration12(statement)
- }
- if (v < 3) {
- migration23(statement)
- }
- if (v < 4) {
- migration34()
- }
- if (v < 5) {
- migration45()
- }
- if (v < 6) {
- migration56()
- }
- if (v < 7) {
- migration67()
- }
+ case Some(v) if v < 7 => throw new RuntimeException("You are updating from a version of eclair older than v0.13: please update to the v0.13 release first to migrate your channel data, and afterwards you'll be able to update to the latest version.")
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
index 36eaa37..5c067cd 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
@@ -17,6 +17,7 @@
package fr.acinq.eclair.json
import com.google.common.net.HostAndPort
+import fr.acinq.bitcoin.crypto.musig2.IndividualNonce
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.scalacompat.{BlockHash, BlockId, Btc, ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction, TxId}
@@ -151,6 +152,10 @@ object ByteVector64Serializer extends MinimalSerializer({
case x: ByteVector64 => JString(x.toHex)
})
+object IndividualNonceSerializer extends MinimalSerializer({
+ case x: IndividualNonce => JString(x.toString)
+})
+
object UInt64Serializer extends MinimalSerializer({
case x: UInt64 => JInt(x.toBigInt)
})
@@ -730,6 +735,7 @@ object JsonSerializers {
BlockIdSerializer +
BlockHashSerializer +
ByteVector64Serializer +
+ IndividualNonceSerializer +
ChannelEventSerializer +
UInt64Serializer +
TimestampSecondSerializer +
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala
index b8f7b20..12806b0 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala
@@ -17,17 +17,11 @@
package fr.acinq.eclair.wire.internal.channel
import fr.acinq.eclair.channel.PersistentChannelData
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelCodecs0
-import fr.acinq.eclair.wire.internal.channel.version1.ChannelCodecs1
-import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2
-import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3
-import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4
import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5
import grizzled.slf4j.Logging
-import scodec.Codec
-import scodec.codecs.{byte, discriminated}
+import scodec.codecs.{byte, discriminated, fail}
+import scodec.{Codec, Err}
-// @formatter:off
/**
* Codecs used to store the internal channel data.
*
@@ -37,28 +31,33 @@ import scodec.codecs.{byte, discriminated}
* 1) [[ChannelCodecs]] is the only publicly accessible class. It handles compatibility between different versions
* of the codecs.
*
- * 2) Each codec version must be in its separate package, and have the following structure:
+ * 2) Each codec version must be in its separate package (version0, version1, etc), and have the following structure:
* {{{
- * private[channel] object ChannelCodecs0 {
-
- private[version0] object Codecs {
-
- // internal codecs
-
- }
-
- val channelDataCodec: Codec[PersistentChannelData] = ...
+ * private[channel] object ChannelCodecsN {
+ *
+ * private[versionN] object Codecs {
+ *
+ * // internal codecs
+ *
+ * }
+ *
+ * val channelDataCodec: Codec[PersistentChannelData] = ...
* }}}
*
- * Notice that the outer class has a visibility restricted to package [[fr.acinq.eclair.wire.internal.channel]], while the inner class has a
- * visibility restricted to package [[version0]]. This guarantees that we strictly segregate each codec version,
- * while still allowing unitary testing.
+ * Notice that the outer class has a visibility restricted to package [[fr.acinq.eclair.wire.internal.channel]], while
+ * the inner class has a visibility restricted to package [[versionN]]. This guarantees that we strictly segregate each
+ * codec version, while still allowing unitary testing.
*
* Created by PM on 02/06/2017.
*/
-// @formatter:on
object ChannelCodecs extends Logging {
+ /**
+ * Codecs v0 to v4 have been removed after the eclair v0.13 release.
+ * Users on older version will need to first run the v0.13 release before updating to a newer version.
+ */
+ private val pre013FailingCodec: Codec[PersistentChannelData] = fail(Err("You are updating from a version of eclair older than v0.13: please update to the v0.13 release first to migrate your channel data, and afterwards you'll be able to update to the latest version."))
+
/**
* Order matters!!
*
@@ -69,10 +68,10 @@ object ChannelCodecs extends Logging {
*/
val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(byte)
.typecase(5, ChannelCodecs5.channelDataCodec)
- .typecase(4, ChannelCodecs4.channelDataCodec.decodeOnly)
- .typecase(3, ChannelCodecs3.channelDataCodec.decodeOnly)
- .typecase(2, ChannelCodecs2.channelDataCodec.decodeOnly)
- .typecase(1, ChannelCodecs1.channelDataCodec.decodeOnly)
- .typecase(0, ChannelCodecs0.channelDataCodec.decodeOnly)
+ .typecase(4, pre013FailingCodec)
+ .typecase(3, pre013FailingCodec)
+ .typecase(2, pre013FailingCodec)
+ .typecase(1, pre013FailingCodec)
+ .typecase(0, pre013FailingCodec)
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
deleted file mode 100644
index 0762700..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Copyright 2019 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version0
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
-import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxId, TxOut}
-import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys}
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.transactions._
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSigs, PublishableTxs}
-import fr.acinq.eclair.wire.protocol.CommonCodecs._
-import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, combinedFeaturesCodec}
-import fr.acinq.eclair.wire.protocol._
-import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TimestampSecond}
-import scodec.Codec
-import scodec.bits.{BitVector, ByteVector, HexStringSyntax}
-import scodec.codecs._
-import shapeless.{::, HNil}
-
-import java.util.UUID
-
-/**
- * Those codecs are here solely for backward compatibility reasons.
- *
- * Created by PM on 02/06/2017.
- */
-private[channel] object ChannelCodecs0 {
-
- private[version0] object Codecs {
-
- val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath].decodeOnly
-
- val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = discriminatorWithDefault[ChannelTypes0.ChannelVersion](
- discriminator = discriminated[ChannelTypes0.ChannelVersion].by(byte)
- .typecase(0x01, bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion])
- // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons
- ,
- fallback = provide(ChannelTypes0.ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version
- // field and don't support additional features which is why all bits are set to 0.
- )
-
- def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = (
- ("nodeId" | publicKey) ::
- ("channelPath" | keyPathCodec) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("isInitiator" | bool) ::
- ("upfrontShutdownScript_opt" | varsizebinarydata.map(Option(_)).decodeOnly) ::
- ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) ::
- ("features" | combinedFeaturesCodec)).map {
- case nodeId :: channelPath :: dustLimit :: maxHtlcValueInFlightMsat :: channelReserve :: htlcMinimum :: toSelfDelay :: maxAcceptedHtlcs :: isInitiator :: upfrontShutdownScript_opt :: walletStaticPaymentBasepoint :: features :: HNil =>
- ChannelTypes0.LocalParams(nodeId, channelPath, dustLimit, maxHtlcValueInFlightMsat, channelReserve, htlcMinimum, toSelfDelay, maxAcceptedHtlcs, isInitiator, isInitiator, upfrontShutdownScript_opt, walletStaticPaymentBasepoint, features)
- }.decodeOnly
-
- val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = (
- ("nodeId" | publicKey) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("fundingPubKey" | publicKey) ::
- ("revocationBasepoint" | publicKey) ::
- ("paymentBasepoint" | publicKey) ::
- ("delayedPaymentBasepoint" | publicKey) ::
- ("htlcBasepoint" | publicKey) ::
- ("features" | combinedFeaturesCodec) ::
- ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams].decodeOnly
-
- val updateAddHtlcCodec: Codec[UpdateAddHtlc] = (
- ("channelId" | bytes32) ::
- ("id" | uint64overflow) ::
- ("amountMsat" | millisatoshi) ::
- ("paymentHash" | bytes32) ::
- ("expiry" | cltvExpiry) ::
- ("onionRoutingPacket" | PaymentOnionCodecs.paymentOnionPacketCodec) ::
- ("tlvStream" | provide(TlvStream.empty[UpdateAddHtlcTlv]))).as[UpdateAddHtlc]
-
- val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool)
- .typecase(true, updateAddHtlcCodec.as[IncomingHtlc])
- .typecase(false, updateAddHtlcCodec.as[OutgoingHtlc])
-
- def setCodec[T](codec: Codec[T]): Codec[Set[T]] = Codec[Set[T]](
- (elems: Set[T]) => listOfN(uint16, codec).encode(elems.toList),
- (wire: BitVector) => listOfN(uint16, codec).decode(wire).map(_.map(_.toSet))
- )
-
- val commitmentSpecCodec: Codec[CommitmentSpec] = (
- ("htlcs" | setCodec(htlcCodec)) ::
- ("feeratePerKw" | feeratePerKw) ::
- ("toLocal" | millisatoshi) ::
- ("toRemote" | millisatoshi)).as[CommitmentSpec].decodeOnly
-
- val outPointCodec: Codec[OutPoint] = variableSizeBytes(uint16, bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))
-
- val txOutCodec: Codec[TxOut] = variableSizeBytes(uint16, bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))
-
- val txCodec: Codec[Transaction] = variableSizeBytes(uint16, bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
-
- val closingTxCodec: Codec[ClosingTx] = txCodec.decodeOnly.xmap(
- tx => ChannelTypes0.migrateClosingTx(tx),
- closingTx => closingTx.tx
- )
-
- val inputInfoCodec: Codec[InputInfo] = (
- ("outPoint" | outPointCodec) ::
- ("txOut" | txOutCodec) ::
- ("redeemScript" | varsizebinarydata)).map {
- case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut)
- }.decodeOnly
-
- private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0))
- private val missingPaymentHash: Codec[ByteVector32] = provide(ByteVector32.Zeroes)
- private val missingToSelfDelay: Codec[CltvExpiryDelta] = provide(CltvExpiryDelta(0))
- // Those fields have been added to our transactions after we stopped storing them in our channel data, so they're safe to ignore.
- private val unusedCommitmentFormat: Codec[CommitmentFormat] = provide(DefaultCommitmentFormat)
- private val dummyPrivateKey = PrivateKey(hex"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1")
- private val dummyPublicKey = dummyPrivateKey.publicKey
- private val unusedRemoteCommitKeys: Codec[RemoteCommitmentKeys] = provide(RemoteCommitmentKeys(Right(dummyPrivateKey), dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedLocalCommitKeys: Codec[LocalCommitmentKeys] = provide(LocalCommitmentKeys(dummyPrivateKey, dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedRevocationKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevokedRedeemInfo: Codec[RedeemInfo] = provide(RedeemInfo.P2wsh(Nil))
-
- // We can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a
- // downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing.
- // We can also safely set confirmBefore = 0: we will simply use a high feerate to make these transactions confirm
- // as quickly as possible. It's very unlikely that nodes will run into this, so it's a good trade-off between code
- // complexity and real world impact.
- val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16)
- .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
- .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcSuccessTx])
- .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcTimeoutTx])
- .typecase(0x04, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx])
- .typecase(0x05, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcTimeoutTx])
- .typecase(0x06, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimP2WPKHOutputTx])
- .typecase(0x07, (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimLocalDelayedOutputTx])
- .typecase(0x08, (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[MainPenaltyTx])
- .typecase(0x09, (unusedRemoteCommitKeys :: unusedRevocationKey :: unusedRevokedRedeemInfo :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[HtlcPenaltyTx])
- .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[Long]))).as[ClosingTx])
-
- // this is a backward compatible codec (we used to store the sig as DER encoded), now we store it as 64-bytes
- val sig64OrDERCodec: Codec[ByteVector64] = Codec[ByteVector64](
- (value: ByteVector64) => bytes(64).encode(value),
- (wire: BitVector) => bytes.decode(wire).map(_.map {
- case bin64 if bin64.size == 64 => ByteVector64(bin64)
- case der => Crypto.der2compact(der)
- })
- )
-
- val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = (
- ("txinfo" | txWithInputInfoCodec.downcast[UnsignedHtlcTx]) ::
- ("localSig" | variableSizeBytes(uint16, sig64OrDERCodec)) :: // we store as variable length for historical purposes (we used to store as DER encoded)
- ("remoteSig" | variableSizeBytes(uint16, sig64OrDERCodec))).as[HtlcTxAndSigs].decodeOnly
-
- val publishableTxsCodec: Codec[PublishableTxs] = (
- ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) ::
- ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs].decodeOnly
-
- val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly
-
- val remoteCommitCodec: Codec[RemoteCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("txid" | txId) ::
- ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit].decodeOnly
-
- val updateFulfillHtlcCodec: Codec[UpdateFulfillHtlc] = (
- ("channelId" | bytes32) ::
- ("id" | uint64overflow) ::
- ("paymentPreimage" | bytes32) ::
- ("tlvStream" | provide(TlvStream.empty[UpdateFulfillHtlcTlv]))).as[UpdateFulfillHtlc]
-
- val updateFailHtlcCodec: Codec[UpdateFailHtlc] = (
- ("channelId" | bytes32) ::
- ("id" | uint64overflow) ::
- ("reason" | varsizebinarydata) ::
- ("tlvStream" | provide(TlvStream.empty[UpdateFailHtlcTlv]))).as[UpdateFailHtlc]
-
- val updateFailMalformedHtlcCodec: Codec[UpdateFailMalformedHtlc] = (
- ("channelId" | bytes32) ::
- ("id" | uint64overflow) ::
- ("onionHash" | bytes32) ::
- ("failureCode" | uint16) ::
- ("tlvStream" | provide(TlvStream.empty[UpdateFailMalformedHtlcTlv]))).as[UpdateFailMalformedHtlc]
-
- val updateFeeCodec: Codec[UpdateFee] = (
- ("channelId" | bytes32) ::
- ("feeratePerKw" | feeratePerKw) ::
- ("tlvStream" | provide(TlvStream.empty[UpdateFeeTlv]))).as[UpdateFee]
-
- val updateMessageCodec: Codec[UpdateMessage] = discriminated[UpdateMessage].by(uint16)
- .typecase(128, updateAddHtlcCodec)
- .typecase(130, updateFulfillHtlcCodec)
- .typecase(131, updateFailHtlcCodec)
- .typecase(134, updateFeeCodec)
- .typecase(135, updateFailMalformedHtlcCodec)
-
- val localChangesCodec: Codec[LocalChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges].decodeOnly
-
- val remoteChangesCodec: Codec[RemoteChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges].decodeOnly
-
- val commitSigCodec: Codec[CommitSig] = (
- ("channelId" | bytes32) ::
- ("signature" | bytes64.as[ChannelSpendSignature.IndividualSignature]) ::
- ("htlcSignatures" | listofsignatures) ::
- ("tlvStream" | provide(TlvStream.empty[CommitSigTlv]))).as[CommitSig]
-
- val waitingForRevocationCodec: Codec[ChannelTypes0.WaitingForRevocation] = (
- ("nextRemoteCommit" | remoteCommitCodec) ::
- ("sent" | commitSigCodec) ::
- ("sentAfterLocalCommitIndex" | uint64overflow) ::
- ("reSignAsap" | ignore(1))).as[ChannelTypes0.WaitingForRevocation].decodeOnly
-
- val upstreamLocalCodec: Codec[Upstream.Local] = ("id" | uuid).as[Upstream.Local]
-
- val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | millisatoshi) ::
- ("amountOut" | ignore(64))).as[Upstream.Cold.Channel]
-
- val upstreamChannelWithoutAmountCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | provide(0 msat))).as[Upstream.Cold.Channel]
-
- val upstreamTrampolineCodec: Codec[Upstream.Cold.Trampoline] = listOfN(uint16, upstreamChannelWithoutAmountCodec).as[Upstream.Cold.Trampoline]
-
- // this is for backward compatibility to handle legacy payments that didn't have identifiers
- val UNKNOWN_UUID: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000")
-
- val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16)
- .typecase(0x03, upstreamLocalCodec) // backward compatible
- .typecase(0x01, provide(Upstream.Local(UNKNOWN_UUID)))
- .typecase(0x02, upstreamChannelCodec)
- .typecase(0x04, upstreamTrampolineCodec)
-
- val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin](
- upstream => Origin.Cold(upstream),
- {
- case Origin.Hot(_, upstream) => Upstream.Cold(upstream)
- case Origin.Cold(upstream) => upstream
- }
- )
-
- val originsListCodec: Codec[List[(Long, Origin)]] = listOfN(uint16, int64 ~ originCodec)
-
- val originsMapCodec: Codec[Map[Long, Origin]] = Codec[Map[Long, Origin]](
- (map: Map[Long, Origin]) => originsListCodec.encode(map.toList),
- (wire: BitVector) => originsListCodec.decode(wire).map(_.map(_.toMap))
- )
-
- val spentListCodec: Codec[List[(OutPoint, TxId)]] = listOfN(uint16, outPointCodec ~ txId)
-
- val spentMapCodec: Codec[Map[OutPoint, TxId]] = Codec[Map[OutPoint, TxId]](
- (map: Map[OutPoint, TxId]) => spentListCodec.encode(map.toList),
- (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap))
- )
-
- val commitmentsCodec: Codec[Commitments] = (
- ("channelVersion" | channelVersionCodec) >>:~ { channelVersion =>
- ("localParams" | localParamsCodec(channelVersion)) ::
- ("remoteParams" | remoteParamsCodec) ::
- ("channelFlags" | channelflags) ::
- ("localCommit" | localCommitCodec) ::
- ("remoteCommit" | remoteCommitCodec) ::
- ("localChanges" | localChangesCodec) ::
- ("remoteChanges" | remoteChangesCodec) ::
- ("localNextHtlcId" | uint64overflow) ::
- ("remoteNextHtlcId" | uint64overflow) ::
- ("originChannels" | originsMapCodec) ::
- ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) ::
- ("commitInput" | inputInfoCodec) ::
- ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) ::
- ("channelId" | bytes32)
- }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly
-
- val closingSignedCodec: Codec[ClosingSigned] = (
- ("channelId" | bytes32) ::
- ("feeSatoshis" | satoshi) ::
- ("signature" | bytes64) ::
- ("tlvStream" | provide(TlvStream.empty[ClosingSignedTlv]))).as[ClosingSigned]
-
- val closingTxProposedCodec: Codec[ClosingTxProposed] = (
- ("unsignedTx" | closingTxCodec) ::
- ("localClosingSigned" | closingSignedCodec)).as[ClosingTxProposed].decodeOnly
-
- val localCommitPublishedCodec: Codec[LocalCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainDelayedOutputTx" | optional(bool, txCodec)) ::
- ("htlcSuccessTxs" | listOfN(uint16, txCodec)) ::
- ("htlcTimeoutTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcDelayedTx" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly
-
- val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool, txCodec)) ::
- ("claimHtlcSuccessTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcTimeoutTxs" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly
-
- val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool, txCodec)) ::
- ("mainPenaltyTx" | optional(bool, txCodec)) ::
- ("htlcPenaltyTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly
-
- // All channel_announcement's written prior to supporting unknown trailing fields had the same fixed size, because
- // those are the announcements that *we* created and we always used an empty features field, which was the only
- // variable-length field.
- val noUnknownFieldsChannelAnnouncementSizeCodec: Codec[Int] = provide(430)
-
- // We used to ignore unknown trailing fields, and assume that channel_update size was known. This is not true anymore,
- // so we need to tell the codec where to stop, otherwise all the remaining part of the data will be decoded as unknown
- // fields. Fortunately, we can easily tell what size the channel_update will be.
- val noUnknownFieldsChannelUpdateSizeCodec: Codec[Int] = peek( // we need to take a peek at a specific byte to know what size the message will be, and then rollback to read the full message
- ignore(8 * (64 + 32 + 8 + 4)) ~> // we skip the first fields: signature + chain_hash + short_channel_id + timestamp
- byte // this is the messageFlags byte
- )
- .map(messageFlags => if ((messageFlags & 1) != 0) 136 else 128) // depending on the value of option_channel_htlc_max, size will be 128B or 136B
- .decodeOnly // this is for compat, we only need to decode
-
- val fundingCreatedCodec: Codec[FundingCreated] = (
- ("temporaryChannelId" | bytes32) ::
- ("fundingTxHash" | txIdAsHash) ::
- ("fundingOutputIndex" | uint16) ::
- ("signature" | bytes64) ::
- ("tlvStream" | provide(TlvStream.empty[FundingCreatedTlv]))).as[FundingCreated]
-
- val fundingSignedCodec: Codec[FundingSigned] = (
- ("channelId" | bytes32) ::
- ("signature" | bytes64) ::
- ("tlvStream" | provide(TlvStream.empty[FundingSignedTlv]))).as[FundingSigned]
-
- val channelReadyCodec: Codec[ChannelReady] = (
- ("channelId" | bytes32) ::
- ("nextPerCommitmentPoint" | publicKey) ::
- ("tlvStream" | provide(TlvStream.empty[ChannelReadyTlv]))).as[ChannelReady]
-
- // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | provide[Option[Transaction]](None)) ::
- ("waitingSince" | provide(BlockHeight(TimestampSecond.now().toLong))) ::
- ("deferred" | optional(bool, channelReadyCodec)) ::
- ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).map {
- case commitments :: fundingTx :: waitingSince :: deferred :: lastSent :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx))
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments1, waitingSince, deferred, lastSent)
- }.decodeOnly
-
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_08_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("deferred" | optional(bool, channelReadyCodec)) ::
- ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).map {
- case commitments :: fundingTx :: waitingSince :: deferred :: lastSent :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx))
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments1, waitingSince, deferred, lastSent)
- }.decodeOnly
-
- val DATA_WAIT_FOR_CHANNEL_READY_02_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("lastSent" | channelReadyCodec)).map {
- case commitments :: shortChannelId :: _ :: HNil =>
- DATA_WAIT_FOR_CHANNEL_READY(commitments, aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None))
- }.decodeOnly
-
- val shutdownCodec: Codec[Shutdown] = (
- ("channelId" | bytes32) ::
- ("scriptPubKey" | varsizebinarydata) ::
- ("tlvStream" | provide(TlvStream.empty[ShutdownTlv]))).as[Shutdown]
-
- // this is a decode-only codec compatible with versions 9afb26e and below
- val DATA_NORMAL_03_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool) ::
- ("channelAnnouncement" | optional(bool, variableSizeBytes(noUnknownFieldsChannelAnnouncementSizeCodec, channelAnnouncementCodec))) ::
- ("channelUpdate" | variableSizeBytes(noUnknownFieldsChannelUpdateSizeCodec, channelUpdateCodec)) ::
- ("localShutdown" | optional(bool, shutdownCodec)) ::
- ("remoteShutdown" | optional(bool, shutdownCodec)) ::
- ("closeStatus" | provide(Option.empty[CloseStatus]))).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates)
- }.decodeOnly
-
- val DATA_NORMAL_10_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool) ::
- ("channelAnnouncement" | optional(bool, variableSizeBytes(uint16, channelAnnouncementCodec))) ::
- ("channelUpdate" | variableSizeBytes(uint16, channelUpdateCodec)) ::
- ("localShutdown" | optional(bool, shutdownCodec)) ::
- ("remoteShutdown" | optional(bool, shutdownCodec)) ::
- ("closeStatus" | provide(Option.empty[CloseStatus]))).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates)
- }.decodeOnly
-
- val DATA_SHUTDOWN_04_Codec: Codec[DATA_SHUTDOWN] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | shutdownCodec) ::
- ("remoteShutdown" | shutdownCodec) ::
- ("closeStatus" | provide[CloseStatus](CloseStatus.Initiator(None)))).as[DATA_SHUTDOWN].decodeOnly
-
- val DATA_NEGOTIATING_05_Codec: Codec[DATA_NEGOTIATING] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | shutdownCodec) ::
- ("remoteShutdown" | shutdownCodec) ::
- ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) ::
- ("bestUnpublishedClosingTx_opt" | optional(bool, closingTxCodec))).as[DATA_NEGOTIATING].decodeOnly
-
- // this is a decode-only codec compatible with versions 818199e and below, with placeholders for new fields
- val DATA_CLOSING_06_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | provide[Option[Transaction]](None)) ::
- ("waitingSince" | provide(BlockHeight(TimestampSecond.now().toLong))) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt))
- DATA_CLOSING(commitments1, waitingSince, commitments1.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val DATA_CLOSING_09_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt))
- DATA_CLOSING(commitments1, waitingSince, commitments1.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val channelReestablishCodec: Codec[ChannelReestablish] = (
- ("channelId" | bytes32) ::
- ("nextLocalCommitmentNumber" | uint64overflow) ::
- ("nextRemoteRevocationNumber" | uint64overflow) ::
- ("yourLastPerCommitmentSecret" | privateKey) ::
- ("myCurrentPerCommitmentPoint" | publicKey) ::
- ("tlvStream" | provide(TlvStream.empty[ChannelReestablishTlv]))).as[ChannelReestablish]
-
- val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_07_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = (
- ("commitments" | commitmentsCodec) ::
- ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT].decodeOnly
- }
-
- // Order matters!
- val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16)
- .typecase(0x10, Codecs.DATA_NORMAL_10_Codec)
- .typecase(0x09, Codecs.DATA_CLOSING_09_Codec)
- .typecase(0x08, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_08_Codec)
- .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_01_Codec)
- .typecase(0x02, Codecs.DATA_WAIT_FOR_CHANNEL_READY_02_Codec)
- .typecase(0x03, Codecs.DATA_NORMAL_03_Codec)
- .typecase(0x04, Codecs.DATA_SHUTDOWN_04_Codec)
- .typecase(0x05, Codecs.DATA_NEGOTIATING_05_Codec)
- .typecase(0x06, Codecs.DATA_CLOSING_06_Codec)
- .typecase(0x07, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_07_Codec)
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala
deleted file mode 100644
index 568cafb..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2021 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version0
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, DeterministicWallet, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxId, TxOut}
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.transactions.CommitmentSpec
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.wire.protocol.CommitSig
-import fr.acinq.eclair.{CltvExpiryDelta, Features, InitFeature, MilliSatoshi, UInt64, channel}
-import scodec.bits.{BitVector, ByteVector}
-
-private[channel] object ChannelTypes0 {
-
- // The format of the XxxCommitPublished types was changed in version2 to work with anchor outputs channels.
- // Before that, all closing txs were generated once (when we detected the force-close) and never updated afterwards
- // (with the exception of 3rd-stage penalty transactions for revoked commitments when one of their htlc txs wins the
- // race against our htlc-penalty tx, but if that happens a `WatchSpent` will be triggered and we will claim it correctly).
- // When migrating from these previous types, we can safely set dummy values in the following fields:
- // - we only use the `tx` field of `TransactionWithInputInfo` -> no need to completely fill the `InputInfo`
- // - `irrevocablySpent` now contains the whole transaction (previously only the txid): we can easily set these when
- // one of *our* transactions confirmed, but not when a *remote* transaction confirms. This can only happen for HTLC
- // outputs and in these cases we simply remove the entry in `irrevocablySpent`: the channel will set a `WatchSpent`
- // which will immediately be triggered and that will let us store the information in `irrevocablySpent`.
- // - the `htlcId` in htlc txs is used to detect timed out htlcs and relay them upstream, but it can be safely set to
- // 0 because the `timedOutHtlcs` in `Helpers.scala` explicitly handle the case where this information is unavailable.
-
- case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) {
- def migrate(): channel.LocalCommitPublished = {
- val htlcTxs = htlcSuccessTxs ++ htlcTimeoutTxs
- val knownTxs: Map[TxId, Transaction] = (commitTx :: claimMainDelayedOutputTx.toList ::: htlcTxs ::: claimHtlcDelayedTxs).map(tx => tx.txid -> tx).toMap
- // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but
- // the channel will put a watch at start-up which will make us fetch the spending transaction.
- val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
- val localOutput_opt = claimMainDelayedOutputTx.map(_.txIn.head.outPoint)
- val incomingHtlcs = htlcSuccessTxs.map(tx => tx.txIn.head.outPoint -> 0L).toMap
- val outgoingHtlcs = htlcTimeoutTxs.map(tx => tx.txIn.head.outPoint -> 0L).toMap
- val htlcDelayedOutputs = claimHtlcDelayedTxs.map(_.txIn.head.outPoint).toSet
- channel.LocalCommitPublished(commitTx, localOutput_opt, anchorOutput_opt = None, incomingHtlcs = incomingHtlcs, outgoingHtlcs = outgoingHtlcs, htlcDelayedOutputs, irrevocablySpentNew)
- }
- }
-
- case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) {
- def migrate(): channel.RemoteCommitPublished = {
- val claimHtlcTxs = claimHtlcSuccessTxs ::: claimHtlcTimeoutTxs
- val knownTxs: Map[TxId, Transaction] = (commitTx :: claimMainOutputTx.toList ::: claimHtlcTxs).map(tx => tx.txid -> tx).toMap
- // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but
- // the channel will put a watch at start-up which will make us fetch the spending transaction.
- val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
- val localOutput_opt = claimMainOutputTx.map(_.txIn.head.outPoint)
- val incomingHtlcs = claimHtlcSuccessTxs.map(tx => tx.txIn.head.outPoint -> 0L).toMap
- val outgoingHtlcs = claimHtlcTimeoutTxs.map(tx => tx.txIn.head.outPoint -> 0L).toMap
- channel.RemoteCommitPublished(commitTx, localOutput_opt, anchorOutput_opt = None, incomingHtlcs = incomingHtlcs, outgoingHtlcs = outgoingHtlcs, irrevocablySpentNew)
- }
- }
-
- case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], htlcPenaltyTxs: List[Transaction], claimHtlcDelayedPenaltyTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) {
- def migrate(): channel.RevokedCommitPublished = {
- val knownTxs: Map[TxId, Transaction] = (commitTx :: claimMainOutputTx.toList ::: mainPenaltyTx.toList ::: htlcPenaltyTxs ::: claimHtlcDelayedPenaltyTxs).map(tx => tx.txid -> tx).toMap
- // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but
- // the channel will put a watch at start-up which will make us fetch the spending transaction.
- val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
- val localOutput_opt = claimMainOutputTx.map(_.txIn.head.outPoint)
- val remoteOutput_opt = mainPenaltyTx.map(_.txIn.head.outPoint)
- val htlcOutputs = htlcPenaltyTxs.map(_.txIn.head.outPoint).toSet
- val htlcDelayedOutputs = claimHtlcDelayedPenaltyTxs.map(_.txIn.head.outPoint).toSet
- channel.RevokedCommitPublished(commitTx, localOutput_opt, remoteOutput_opt, htlcOutputs, htlcDelayedOutputs, irrevocablySpentNew)
- }
- }
-
- def setFundingStatus(commitments: fr.acinq.eclair.channel.Commitments, status: LocalFundingStatus): fr.acinq.eclair.channel.Commitments = {
- commitments.copy(
- active = commitments.active.head.copy(localFundingStatus = status) +: commitments.active.tail
- )
- }
-
- /**
- * Starting with version2, we store a complete ClosingTx object for mutual close scenarios instead of simply storing
- * the raw transaction. It provides more information for auditing but is not used for business logic, so we can safely
- * put dummy values in the migration.
- */
- def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil)), tx, None)
-
- case class HtlcTxAndSigs(txinfo: UnsignedHtlcTx, localSig: ByteVector64, remoteSig: ByteVector64)
-
- case class PublishableTxs(commitTx: CommitTx, htlcTxsAndSigs: List[HtlcTxAndSigs])
-
- // Before version3, we stored fully signed local transactions (commit tx and htlc txs). It meant that someone gaining
- // access to the database could publish revoked commit txs, so we changed that to only store remote signatures.
- case class LocalCommit(index: Long, spec: CommitmentSpec, publishableTxs: PublishableTxs) {
- def migrate(remoteFundingPubKey: PublicKey): (channel.LocalCommit, InputInfo) = {
- val remoteSig = extractRemoteSig(publishableTxs.commitTx, remoteFundingPubKey)
- val unsignedCommitTx = publishableTxs.commitTx.copy(tx = removeWitnesses(publishableTxs.commitTx.tx))
- val htlcRemoteSigs = publishableTxs.htlcTxsAndSigs.map(_.remoteSig)
- (channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, remoteSig, htlcRemoteSigs), unsignedCommitTx.input)
- }
-
- private def extractRemoteSig(commitTx: CommitTx, remoteFundingPubKey: PublicKey): ChannelSpendSignature.IndividualSignature = {
- require(commitTx.tx.txIn.size == 1, s"commit tx must have exactly one input, found ${commitTx.tx.txIn.size}")
- val ScriptWitness(Seq(_, sig1, sig2, redeemScript)) = commitTx.tx.txIn.head.witness
- val _ :: OP_PUSHDATA(pub1, _) :: OP_PUSHDATA(pub2, _) :: _ :: OP_CHECKMULTISIG :: Nil = Script.parse(redeemScript)
- require(pub1 == remoteFundingPubKey.value || pub2 == remoteFundingPubKey.value, "unrecognized funding pubkey")
- if (pub1 == remoteFundingPubKey.value) {
- ChannelSpendSignature.IndividualSignature(Crypto.der2compact(sig1))
- } else {
- ChannelSpendSignature.IndividualSignature(Crypto.der2compact(sig2))
- }
- }
- }
-
- private def removeWitnesses(tx: Transaction): Transaction = tx.copy(txIn = tx.txIn.map(_.copy(witness = ScriptWitness.empty)))
-
- // Before version3, we had a ChannelVersion field describing what channel features were activated. It was mixing
- // official features (static_remotekey, anchor_outputs) and internal features (channel key derivation scheme).
- // We separated this into two separate fields in version3:
- // - a channel type field containing the channel Bolt 9 features
- // - an internal channel configuration field
- case class ChannelVersion(bits: BitVector) {
- // @formatter:off
- def isSet(bit: Int): Boolean = bits.reverse.get(bit)
- def |(other: ChannelVersion): ChannelVersion = ChannelVersion(bits | other.bits)
-
- def hasPubkeyKeyPath: Boolean = isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT)
- def hasStaticRemotekey: Boolean = isSet(ChannelVersion.USE_STATIC_REMOTEKEY_BIT)
- def hasAnchorOutputs: Boolean = isSet(ChannelVersion.USE_ANCHOR_OUTPUTS_BIT)
- def paysDirectlyToWallet: Boolean = hasStaticRemotekey && !hasAnchorOutputs
- // @formatter:on
- }
-
- object ChannelVersion {
-
- import scodec.bits._
-
- val LENGTH_BITS: Int = 4 * 8
-
- private val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
- private val USE_STATIC_REMOTEKEY_BIT = 1
- private val USE_ANCHOR_OUTPUTS_BIT = 2
-
- def fromBit(bit: Int): ChannelVersion = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse)
-
- val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
- val STANDARD = ZEROES | fromBit(USE_PUBKEY_KEYPATH_BIT)
- val STATIC_REMOTEKEY = STANDARD | fromBit(USE_STATIC_REMOTEKEY_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY
- val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS
- }
-
- case class LocalParams(nodeId: PublicKey,
- fundingKeyPath: DeterministicWallet.KeyPath,
- dustLimit: Satoshi,
- maxHtlcValueInFlightMsat: UInt64,
- initialRequestedChannelReserve_opt: Option[Satoshi],
- htlcMinimum: MilliSatoshi,
- toSelfDelay: CltvExpiryDelta,
- maxAcceptedHtlcs: Int,
- isChannelOpener: Boolean,
- paysCommitTxFees: Boolean,
- upfrontShutdownScript_opt: Option[ByteVector],
- walletStaticPaymentBasepoint: Option[PublicKey],
- initFeatures: Features[InitFeature]) {
- def migrate(): channel.LocalChannelParams = channel.LocalChannelParams(
- nodeId = nodeId,
- fundingKeyPath = fundingKeyPath,
- initialRequestedChannelReserve_opt = initialRequestedChannelReserve_opt,
- isChannelOpener = isChannelOpener,
- paysCommitTxFees = paysCommitTxFees,
- upfrontShutdownScript_opt = upfrontShutdownScript_opt,
- walletStaticPaymentBasepoint = walletStaticPaymentBasepoint,
- initFeatures = initFeatures,
- )
- }
-
- case class RemoteParams(nodeId: PublicKey,
- dustLimit: Satoshi,
- maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
- requestedChannelReserve_opt: Option[Satoshi],
- htlcMinimum: MilliSatoshi,
- toRemoteDelay: CltvExpiryDelta,
- maxAcceptedHtlcs: Int,
- fundingPubKey: PublicKey,
- revocationBasepoint: PublicKey,
- paymentBasepoint: PublicKey,
- delayedPaymentBasepoint: PublicKey,
- htlcBasepoint: PublicKey,
- initFeatures: Features[InitFeature],
- upfrontShutdownScript_opt: Option[ByteVector]) {
- def migrate(): channel.RemoteChannelParams = channel.RemoteChannelParams(
- nodeId = nodeId,
- initialRequestedChannelReserve_opt = requestedChannelReserve_opt,
- revocationBasepoint = revocationBasepoint,
- paymentBasepoint = paymentBasepoint,
- delayedPaymentBasepoint = delayedPaymentBasepoint,
- htlcBasepoint = htlcBasepoint,
- initFeatures = initFeatures,
- upfrontShutdownScript_opt = upfrontShutdownScript_opt
- )
- }
-
- case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long)
-
- case class Commitments(channelVersion: ChannelVersion,
- localParams: LocalParams, remoteParams: RemoteParams,
- channelFlags: ChannelFlags,
- localCommit: LocalCommit, remoteCommit: RemoteCommit,
- localChanges: LocalChanges, remoteChanges: RemoteChanges,
- localNextHtlcId: Long, remoteNextHtlcId: Long,
- originChannels: Map[Long, Origin],
- remoteNextCommitInfo: Either[WaitingForRevocation, PublicKey],
- commitInput: InputInfo,
- remotePerCommitmentSecrets: ShaChain, channelId: ByteVector32) {
- def migrate(): channel.Commitments = {
- val channelConfig = if (channelVersion.hasPubkeyKeyPath) {
- ChannelConfig(ChannelConfig.FundingPubKeyBasedChannelKeyPath)
- } else {
- ChannelConfig()
- }
- val commitmentFormat = if (channelVersion.hasAnchorOutputs) {
- UnsafeLegacyAnchorOutputsCommitmentFormat
- } else {
- DefaultCommitmentFormat
- }
- val (localCommit1, commitInput) = localCommit.migrate(remoteParams.fundingPubKey)
- val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay)
- val remoteCommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay)
- val commitment = Commitment(
- fundingTxIndex = 0,
- firstRemoteCommitIndex = 0,
- fundingInput = commitInput.outPoint,
- fundingAmount = commitInput.txOut.amount,
- remoteFundingPubKey = remoteParams.fundingPubKey,
- // We set an empty funding tx, even if it may be confirmed already (and the channel fully operational). We could
- // have set a specific Unknown status, but it would have forced us to keep it forever. We will retrieve the
- // funding tx when the channel is instantiated, and update the status (possibly immediately if it was confirmed).
- localFundingStatus = LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None),
- remoteFundingStatus = RemoteFundingStatus.Locked,
- commitmentFormat = commitmentFormat,
- localCommitParams = localCommitParams,
- localCommit = localCommit1,
- remoteCommitParams = remoteCommitParams,
- remoteCommit = remoteCommit,
- nextRemoteCommit_opt = remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit))
- )
- channel.Commitments(
- ChannelParams(channelId, channelConfig, ChannelFeatures(), localParams.migrate(), remoteParams.migrate(), channelFlags),
- CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId),
- Seq(commitment),
- inactive = Nil,
- remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)),
- remotePerCommitmentSecrets,
- originChannels
- )
- }
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala
deleted file mode 100644
index 5dd5a8e..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright 2021 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version1
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
-import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxId, TxOut}
-import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys}
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc}
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSigs, PublishableTxs}
-import fr.acinq.eclair.wire.protocol.CommonCodecs._
-import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._
-import fr.acinq.eclair.wire.protocol._
-import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong}
-import scodec.bits.{ByteVector, HexStringSyntax}
-import scodec.codecs._
-import scodec.{Attempt, Codec}
-import shapeless.{::, HNil}
-
-private[channel] object ChannelCodecs1 {
-
- private[version1] object Codecs {
-
- val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath]
-
- val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion]
-
- def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = (
- ("nodeId" | publicKey) ::
- ("channelPath" | keyPathCodec) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) ::
- ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) ::
- ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) ::
- ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams].decodeOnly
-
- val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = (
- ("nodeId" | publicKey) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("fundingPubKey" | publicKey) ::
- ("revocationBasepoint" | publicKey) ::
- ("paymentBasepoint" | publicKey) ::
- ("delayedPaymentBasepoint" | publicKey) ::
- ("htlcBasepoint" | publicKey) ::
- ("features" | combinedFeaturesCodec) ::
- ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams]
-
- def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList)
-
- val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool8)
- .typecase(true, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc])
- .typecase(false, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc])
-
- val commitmentSpecCodec: Codec[CommitmentSpec] = (
- ("htlcs" | setCodec(htlcCodec)) ::
- ("feeratePerKw" | feeratePerKw) ::
- ("toLocal" | millisatoshi) ::
- ("toRemote" | millisatoshi)).as[CommitmentSpec]
-
- val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))
-
- val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))
-
- val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
-
- val closingTxCodec: Codec[ClosingTx] = txCodec.decodeOnly.xmap(
- tx => ChannelTypes0.migrateClosingTx(tx),
- closingTx => closingTx.tx
- )
-
- val inputInfoCodec: Codec[InputInfo] = (
- ("outPoint" | outPointCodec) ::
- ("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).map {
- case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut)
- }.decodeOnly
-
- private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0))
- private val missingPaymentHash: Codec[ByteVector32] = provide(ByteVector32.Zeroes)
- private val missingToSelfDelay: Codec[CltvExpiryDelta] = provide(CltvExpiryDelta(0))
- // Those fields have been added to our transactions after we stopped storing them in our channel data, so they're safe to ignore.
- private val unusedCommitmentFormat: Codec[CommitmentFormat] = provide(DefaultCommitmentFormat)
- private val dummyPrivateKey = PrivateKey(hex"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1")
- private val dummyPublicKey = dummyPrivateKey.publicKey
- private val unusedRemoteCommitKeys: Codec[RemoteCommitmentKeys] = provide(RemoteCommitmentKeys(Right(dummyPrivateKey), dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedLocalCommitKeys: Codec[LocalCommitmentKeys] = provide(LocalCommitmentKeys(dummyPrivateKey, dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedRevocationKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevokedRedeemInfo: Codec[RedeemInfo] = provide(RedeemInfo.P2wsh(Nil))
-
- // NB: we can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a
- // downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing.
- val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16)
- .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
- .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcSuccessTx])
- .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcTimeoutTx])
- .typecase(0x04, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx])
- .typecase(0x05, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | provide(0L)) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcTimeoutTx])
- .typecase(0x06, (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimP2WPKHOutputTx])
- .typecase(0x07, (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimLocalDelayedOutputTx])
- .typecase(0x08, (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[MainPenaltyTx])
- .typecase(0x09, (unusedRemoteCommitKeys :: unusedRevocationKey :: unusedRevokedRedeemInfo :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[HtlcPenaltyTx])
- .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[Long]))).as[ClosingTx])
-
- val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = (
- ("txinfo" | txWithInputInfoCodec.downcast[UnsignedHtlcTx]) ::
- ("localSig" | lengthDelimited(bytes64)) :: // we store as variable length for historical purposes (we used to store as DER encoded)
- ("remoteSig" | lengthDelimited(bytes64))).as[HtlcTxAndSigs]
-
- val publishableTxsCodec: Codec[PublishableTxs] = (
- ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) ::
- ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs]
-
- val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly
-
- val remoteCommitCodec: Codec[RemoteCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("txid" | txId) ::
- ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit]
-
- val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g))
-
- val localChangesCodec: Codec[LocalChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges]
-
- val remoteChangesCodec: Codec[RemoteChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges]
-
- val waitingForRevocationCodec: Codec[ChannelTypes0.WaitingForRevocation] = (
- ("nextRemoteCommit" | remoteCommitCodec) ::
- ("sent" | lengthDelimited(commitSigCodec)) ::
- ("sentAfterLocalCommitIndex" | uint64overflow) ::
- ("reSignAsap" | ignore(8))).as[ChannelTypes0.WaitingForRevocation]
-
- val upstreamLocalCodec: Codec[Upstream.Local] = ("id" | uuid).as[Upstream.Local]
-
- val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | millisatoshi) ::
- ("amountOut" | ignore(64))).as[Upstream.Cold.Channel]
-
- val upstreamChannelWithoutAmountCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | provide(0 msat))).as[Upstream.Cold.Channel]
-
- val upstreamTrampolineCodec: Codec[Upstream.Cold.Trampoline] = listOfN(uint16, upstreamChannelWithoutAmountCodec).as[Upstream.Cold.Trampoline]
-
- val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16)
- .typecase(0x02, upstreamChannelCodec)
- .typecase(0x03, upstreamLocalCodec)
- .typecase(0x04, upstreamTrampolineCodec)
-
- val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin](
- upstream => Origin.Cold(upstream),
- {
- case Origin.Hot(_, upstream) => Upstream.Cold(upstream)
- case Origin.Cold(upstream) => upstream
- }
- )
-
- def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList)
-
- val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec)
-
- val spentMapCodec: Codec[Map[OutPoint, TxId]] = mapCodec(outPointCodec, txId)
-
- val commitmentsCodec: Codec[Commitments] = (
- ("channelVersion" | channelVersionCodec) >>:~ { channelVersion =>
- ("localParams" | localParamsCodec(channelVersion)) ::
- ("remoteParams" | remoteParamsCodec) ::
- ("channelFlags" | channelflags) ::
- ("localCommit" | localCommitCodec) ::
- ("remoteCommit" | remoteCommitCodec) ::
- ("localChanges" | localChangesCodec) ::
- ("remoteChanges" | remoteChangesCodec) ::
- ("localNextHtlcId" | uint64overflow) ::
- ("remoteNextHtlcId" | uint64overflow) ::
- ("originChannels" | originsMapCodec) ::
- ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) ::
- ("commitInput" | inputInfoCodec) ::
- ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) ::
- ("channelId" | bytes32)
- }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly
-
- val closingTxProposedCodec: Codec[ClosingTxProposed] = (
- ("unsignedTx" | closingTxCodec) ::
- ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed]
-
- val localCommitPublishedCodec: Codec[LocalCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainDelayedOutputTx" | optional(bool8, txCodec)) ::
- ("htlcSuccessTxs" | listOfN(uint16, txCodec)) ::
- ("htlcTimeoutTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcDelayedTx" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly
-
- val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, txCodec)) ::
- ("claimHtlcSuccessTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcTimeoutTxs" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly
-
- val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, txCodec)) ::
- ("mainPenaltyTx" | optional(bool8, txCodec)) ::
- ("htlcPenaltyTxs" | listOfN(uint16, txCodec)) ::
- ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes0.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly
-
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_20_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) ::
- ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).map {
- case commitments :: fundingTx :: waitingSince :: deferred :: lastSent :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx))
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments1, waitingSince, deferred, lastSent)
- }.decodeOnly
-
- val DATA_WAIT_FOR_CHANNEL_READY_21_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("lastSent" | lengthDelimited(channelReadyCodec))).map {
- case commitments :: shortChannelId :: _ :: HNil =>
- DATA_WAIT_FOR_CHANNEL_READY(commitments, aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None))
- }.decodeOnly
-
- val DATA_NORMAL_22_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool8) ::
- ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) ::
- ("channelUpdate" | lengthDelimited(channelUpdateCodec)) ::
- ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("closeStatus" | provide(Option.empty[CloseStatus]))).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus)
- }.decodeOnly
-
- val DATA_SHUTDOWN_23_Codec: Codec[DATA_SHUTDOWN] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closeStatus" | provide[CloseStatus](CloseStatus.Initiator(None)))).as[DATA_SHUTDOWN]
-
- val DATA_NEGOTIATING_24_Codec: Codec[DATA_NEGOTIATING] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) ::
- ("bestUnpublishedClosingTx_opt" | optional(bool8, closingTxCodec))).as[DATA_NEGOTIATING]
-
- val DATA_CLOSING_25_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt))
- DATA_CLOSING(commitments1, waitingSince, commitments1.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_26_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = (
- ("commitments" | commitmentsCodec) ::
- ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT]
- }
-
- // Order matters!
- val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16)
- .typecase(0x20, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_20_Codec)
- .typecase(0x21, Codecs.DATA_WAIT_FOR_CHANNEL_READY_21_Codec)
- .typecase(0x22, Codecs.DATA_NORMAL_22_Codec)
- .typecase(0x23, Codecs.DATA_SHUTDOWN_23_Codec)
- .typecase(0x24, Codecs.DATA_NEGOTIATING_24_Codec)
- .typecase(0x25, Codecs.DATA_CLOSING_25_Codec)
- .typecase(0x26, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_26_Codec)
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala
deleted file mode 100644
index bae787c..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 2021 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version2
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
-import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut}
-import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys}
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc}
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSigs, PublishableTxs}
-import fr.acinq.eclair.wire.protocol.CommonCodecs._
-import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._
-import fr.acinq.eclair.wire.protocol._
-import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong}
-import scodec.bits.{ByteVector, HexStringSyntax}
-import scodec.codecs._
-import scodec.{Attempt, Codec}
-import shapeless.{::, HNil}
-
-private[channel] object ChannelCodecs2 {
-
- private[version2] object Codecs {
-
- val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath]
-
- val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = (
- ("secretkeybytes" | bytes32) ::
- ("chaincode" | bytes32) ::
- ("depth" | uint16) ::
- ("path" | keyPathCodec) ::
- ("parent" | int64))
- .map { case a :: b :: c :: d :: e :: HNil => ExtendedPrivateKey(a, b, c, d, e) }
- .decodeOnly
-
- val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion]
-
- def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = (
- ("nodeId" | publicKey) ::
- ("channelPath" | keyPathCodec) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) ::
- ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) ::
- ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) ::
- ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams]
-
- val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = (
- ("nodeId" | publicKey) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(included = true, satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("fundingPubKey" | publicKey) ::
- ("revocationBasepoint" | publicKey) ::
- ("paymentBasepoint" | publicKey) ::
- ("delayedPaymentBasepoint" | publicKey) ::
- ("htlcBasepoint" | publicKey) ::
- ("features" | combinedFeaturesCodec) ::
- ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams]
-
- def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList)
-
- val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool8)
- .typecase(true, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc])
- .typecase(false, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc])
-
- val commitmentSpecCodec: Codec[CommitmentSpec] = (
- ("htlcs" | setCodec(htlcCodec)) ::
- ("feeratePerKw" | feeratePerKw) ::
- ("toLocal" | millisatoshi) ::
- ("toRemote" | millisatoshi)).as[CommitmentSpec]
-
- val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))
-
- val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))
-
- val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
-
- val inputInfoCodec: Codec[InputInfo] = (
- ("outPoint" | outPointCodec) ::
- ("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).map {
- case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut)
- }.decodeOnly
-
- val outputInfoCodec: Codec[Long] = (
- ("index" | uint32) ::
- ("amount" | satoshi) ::
- ("scriptPubKey" | lengthDelimited(bytes))).map {
- case index :: _ :: _ :: HNil => index
- }.decodeOnly
-
- private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0))
- private val missingPaymentHash: Codec[ByteVector32] = provide(ByteVector32.Zeroes)
- private val missingToSelfDelay: Codec[CltvExpiryDelta] = provide(CltvExpiryDelta(0))
- // Those fields have been added to our transactions after we stopped storing them in our channel data, so they're safe to ignore.
- private val unusedCommitmentFormat: Codec[CommitmentFormat] = provide(DefaultCommitmentFormat)
- private val dummyPrivateKey = PrivateKey(hex"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1")
- private val dummyPublicKey = dummyPrivateKey.publicKey
- private val unusedRemoteCommitKeys: Codec[RemoteCommitmentKeys] = provide(RemoteCommitmentKeys(Right(dummyPrivateKey), dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedLocalCommitKeys: Codec[LocalCommitmentKeys] = provide(LocalCommitmentKeys(dummyPrivateKey, dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedFundingKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevocationKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevokedRedeemInfo: Codec[RedeemInfo] = provide(RedeemInfo.P2wsh(Nil))
-
- val htlcSuccessTxCodec: Codec[UnsignedHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcSuccessTx]
- val htlcTimeoutTxCodec: Codec[UnsignedHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcTimeoutTx]
- val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[HtlcDelayedTx]
- val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx]
- val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcTimeoutTx]
- val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimLocalDelayedOutputTx]
- val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimP2WPKHOutputTx]
- val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimRemoteDelayedOutputTx]
- val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[MainPenaltyTx]
- val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: unusedRevokedRedeemInfo :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[HtlcPenaltyTx]
- val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimHtlcDelayedOutputPenaltyTx]
- val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- // We previously created an unused transaction spending the remote anchor (after the 16-blocks delay).
- val unusedRemoteAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]
-
- val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8)
- .typecase(0x01, claimP2WPKHOutputTxCodec)
- .typecase(0x02, claimRemoteDelayedOutputTxCodec)
-
- val claimAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = discriminated[ClaimLocalAnchorTx].by(uint8)
- .typecase(0x01, claimLocalAnchorOutputTxCodec)
- .typecase(0x02, unusedRemoteAnchorOutputTxCodec)
-
- val htlcTxCodec: Codec[UnsignedHtlcTx] = discriminated[UnsignedHtlcTx].by(uint8)
- .typecase(0x01, htlcSuccessTxCodec)
- .typecase(0x02, htlcTimeoutTxCodec)
-
- val claimHtlcTxCodec: Codec[ClaimHtlcTx] = discriminated[ClaimHtlcTx].by(uint8)
- .typecase(0x01, claimHtlcSuccessTxCodec)
- .typecase(0x02, claimHtlcTimeoutTxCodec)
-
- val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = (
- ("txinfo" | htlcTxCodec) ::
- ("localSig" | lengthDelimited(bytes64)) :: // we store as variable length for historical purposes (we used to store as DER encoded)
- ("remoteSig" | lengthDelimited(bytes64))).as[HtlcTxAndSigs]
-
- val publishableTxsCodec: Codec[PublishableTxs] = (
- ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) ::
- ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs]
-
- val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("publishableTxs" | publishableTxsCodec)).as[ChannelTypes0.LocalCommit].decodeOnly
-
- val remoteCommitCodec: Codec[RemoteCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("txid" | txId) ::
- ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit]
-
- val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g))
-
- val localChangesCodec: Codec[LocalChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges]
-
- val remoteChangesCodec: Codec[RemoteChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges]
-
- val waitingForRevocationCodec: Codec[ChannelTypes0.WaitingForRevocation] = (
- ("nextRemoteCommit" | remoteCommitCodec) ::
- ("sent" | lengthDelimited(commitSigCodec)) ::
- ("sentAfterLocalCommitIndex" | uint64overflow) ::
- ("reSignAsap" | ignore(8))).as[ChannelTypes0.WaitingForRevocation]
-
- val upstreamLocalCodec: Codec[Upstream.Local] = ("id" | uuid).as[Upstream.Local]
-
- val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | millisatoshi) ::
- ("amountOut" | ignore(64))).as[Upstream.Cold.Channel]
-
- val upstreamChannelWithoutAmountCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | provide(0 msat))).as[Upstream.Cold.Channel]
-
- val upstreamTrampolineCodec: Codec[Upstream.Cold.Trampoline] = listOfN(uint16, upstreamChannelWithoutAmountCodec).as[Upstream.Cold.Trampoline]
-
- val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16)
- .typecase(0x02, upstreamChannelCodec)
- .typecase(0x03, upstreamLocalCodec)
- .typecase(0x04, upstreamTrampolineCodec)
-
- val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin](
- upstream => Origin.Cold(upstream),
- {
- case Origin.Hot(_, upstream) => Upstream.Cold(upstream)
- case Origin.Cold(upstream) => upstream
- }
- )
-
- def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList)
-
- val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec)
-
- val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec)
-
- val commitmentsCodec: Codec[Commitments] = (
- ("channelVersion" | channelVersionCodec) >>:~ { channelVersion =>
- ("localParams" | localParamsCodec(channelVersion)) ::
- ("remoteParams" | remoteParamsCodec) ::
- ("channelFlags" | channelflags) ::
- ("localCommit" | localCommitCodec) ::
- ("remoteCommit" | remoteCommitCodec) ::
- ("localChanges" | localChangesCodec) ::
- ("remoteChanges" | remoteChangesCodec) ::
- ("localNextHtlcId" | uint64overflow) ::
- ("remoteNextHtlcId" | uint64overflow) ::
- ("originChannels" | originsMapCodec) ::
- ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) ::
- ("commitInput" | inputInfoCodec) ::
- ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) ::
- ("channelId" | bytes32)
- }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly
-
- val closingTxProposedCodec: Codec[ClosingTxProposed] = (
- ("unsignedTx" | closingTxCodec) ::
- ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed]
-
- val localCommitPublishedCodec: Codec[LocalCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainDelayedOutputTx" | optional(bool8, claimLocalDelayedOutputTxCodec)) ::
- ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) ::
- ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxCodec)) ::
- ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly
-
- val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) ::
- ("claimHtlcTxs" | mapCodec(outPointCodec, optional(bool8, claimHtlcTxCodec))) ::
- ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly
-
- val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) ::
- ("mainPenaltyTx" | optional(bool8, mainPenaltyTxCodec)) ::
- ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxCodec)) ::
- ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly
-
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) ::
- ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).map {
- case commitments :: fundingTx :: waitingSince :: deferred :: lastSent :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx))
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments1, waitingSince, deferred, lastSent)
- }.decodeOnly
-
- val DATA_WAIT_FOR_CHANNEL_READY_01_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("lastSent" | lengthDelimited(channelReadyCodec))).map {
- case commitments :: shortChannelId :: _ :: HNil =>
- DATA_WAIT_FOR_CHANNEL_READY(commitments, aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None))
- }.decodeOnly
-
- val DATA_NORMAL_02_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool8) ::
- ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) ::
- ("channelUpdate" | lengthDelimited(channelUpdateCodec)) ::
- ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("closeStatus" | provide(Option.empty[CloseStatus]))).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus)
- }.decodeOnly
-
- val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closeStatus" | provide[CloseStatus](CloseStatus.Initiator(None)))).as[DATA_SHUTDOWN]
-
- val DATA_NEGOTIATING_04_Codec: Codec[DATA_NEGOTIATING] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) ::
- ("bestUnpublishedClosingTx_opt" | optional(bool8, closingTxCodec))).as[DATA_NEGOTIATING]
-
- val DATA_CLOSING_05_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt))
- DATA_CLOSING(commitments1, waitingSince, commitments1.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_06_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = (
- ("commitments" | commitmentsCodec) ::
- ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT]
- }
-
- val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16)
- .typecase(0x00, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec)
- .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_01_Codec)
- .typecase(0x02, Codecs.DATA_NORMAL_02_Codec)
- .typecase(0x03, Codecs.DATA_SHUTDOWN_03_Codec)
- .typecase(0x04, Codecs.DATA_NEGOTIATING_04_Codec)
- .typecase(0x05, Codecs.DATA_CLOSING_05_Codec)
- .typecase(0x06, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_06_Codec)
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala
deleted file mode 100644
index fb3a60e..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2025 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version2
-
-import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction}
-import fr.acinq.eclair.channel
-import fr.acinq.eclair.transactions.Transactions._
-
-private[channel] object ChannelTypes2 {
-
- case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[ClaimLocalDelayedOutputTx], htlcTxs: Map[OutPoint, Option[UnsignedHtlcTx]], claimHtlcDelayedTxs: List[HtlcDelayedTx], claimAnchorTxs: List[ClaimLocalAnchorTx], irrevocablySpent: Map[OutPoint, Transaction]) {
- def migrate(): channel.LocalCommitPublished = channel.LocalCommitPublished(
- commitTx = commitTx,
- localOutput_opt = claimMainDelayedOutputTx.map(_.input.outPoint),
- anchorOutput_opt = claimAnchorTxs.headOption.map(_.input.outPoint),
- incomingHtlcs = htlcTxs.collect {
- case (outpoint, Some(htlcTx: UnsignedHtlcSuccessTx)) => outpoint -> htlcTx.htlcId
- // This case only happens for a received HTLC for which we don't yet have the preimage.
- // We cannot easily find the htlcId, so we just set it to a high value that won't match existing HTLCs.
- // This is fine because it is only used to unwatch HTLC outpoints that were failed downstream, which is just
- // an optimization to go to CLOSED more quickly.
- case (outpoint, None) => outpoint -> 0x00ffffffffffffffL
- },
- outgoingHtlcs = htlcTxs.collect {
- case (outpoint, Some(htlcTx: UnsignedHtlcTimeoutTx)) => outpoint -> htlcTx.htlcId
- },
- htlcDelayedOutputs = claimHtlcDelayedTxs.map(_.input.outPoint).toSet,
- irrevocablySpent = irrevocablySpent
- )
- }
-
- case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], claimHtlcTxs: Map[OutPoint, Option[ClaimHtlcTx]], claimAnchorTxs: List[ClaimLocalAnchorTx], irrevocablySpent: Map[OutPoint, Transaction]) {
- def migrate(): channel.RemoteCommitPublished = channel.RemoteCommitPublished(
- commitTx = commitTx,
- localOutput_opt = claimMainOutputTx.map(_.input.outPoint),
- anchorOutput_opt = claimAnchorTxs.headOption.map(_.input.outPoint),
- incomingHtlcs = claimHtlcTxs.collect {
- case (outpoint, Some(htlcTx: ClaimHtlcSuccessTx)) => outpoint -> htlcTx.htlcId
- // Similarly to LocalCommitPublished above, it is fine to ignore this case.
- case (outpoint, None) => outpoint -> 0x00ffffffffffffffL
- },
- outgoingHtlcs = claimHtlcTxs.collect {
- case (outpoint, Some(htlcTx: ClaimHtlcTimeoutTx)) => outpoint -> htlcTx.htlcId
- },
- irrevocablySpent = irrevocablySpent
- )
- }
-
- case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], mainPenaltyTx: Option[MainPenaltyTx], htlcPenaltyTxs: List[HtlcPenaltyTx], claimHtlcDelayedPenaltyTxs: List[ClaimHtlcDelayedOutputPenaltyTx], irrevocablySpent: Map[OutPoint, Transaction]) {
- def migrate(): channel.RevokedCommitPublished = channel.RevokedCommitPublished(
- commitTx = commitTx,
- localOutput_opt = claimMainOutputTx.map(_.input.outPoint),
- remoteOutput_opt = mainPenaltyTx.map(_.input.outPoint),
- htlcOutputs = htlcPenaltyTxs.map(_.input.outPoint).toSet,
- htlcDelayedOutputs = claimHtlcDelayedPenaltyTxs.map(_.input.outPoint).toSet,
- irrevocablySpent = irrevocablySpent
- )
- }
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala
deleted file mode 100644
index 9259953..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright 2021 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version3
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
-import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut}
-import fr.acinq.eclair.channel.LocalFundingStatus._
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys}
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc}
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0
-import fr.acinq.eclair.wire.internal.channel.version2.ChannelTypes2
-import fr.acinq.eclair.wire.protocol.CommonCodecs._
-import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._
-import fr.acinq.eclair.wire.protocol.UpdateMessage
-import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, InitFeature, MilliSatoshiLong}
-import scodec.bits.{BitVector, ByteVector, HexStringSyntax}
-import scodec.codecs._
-import scodec.{Attempt, Codec, Err}
-import shapeless.{::, HNil}
-
-private[channel] object ChannelCodecs3 {
-
- private[version3] object Codecs {
-
- val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath]
-
- val channelConfigCodec: Codec[ChannelConfig] = lengthDelimited(bytes).xmap(b => {
- val activated: Set[ChannelConfigOption] = b.bits.toIndexedSeq.reverse.zipWithIndex.collect {
- case (true, 0) => ChannelConfig.FundingPubKeyBasedChannelKeyPath
- }.toSet
- ChannelConfig(activated)
- }, cfg => {
- val indices = cfg.options.map(_.supportBit)
- if (indices.isEmpty) {
- ByteVector.empty
- } else {
- // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits.
- var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits
- indices.foreach(i => buffer = buffer.set(i))
- buffer.reverse.bytes
- }
- })
-
- /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */
- val channelFeaturesCodec: Codec[ChannelTypes3.ChannelFeatures] = lengthDelimited(bytes).xmap(
- (b: ByteVector) => ChannelTypes3.ChannelFeatures(Features(b).activated.keySet.collect { case f: InitFeature => f }), // we make no difference between mandatory/optional, both are considered activated
- (cf: ChannelTypes3.ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention
- )
-
- def localParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.LocalParams] = (
- ("nodeId" | publicKey) ::
- ("channelPath" | keyPathCodec) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) ::
- ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) ::
- ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) ::
- ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams]
-
- def remoteParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = (
- ("nodeId" | publicKey) ::
- ("dustLimit" | satoshi) ::
- ("maxHtlcValueInFlightMsat" | uint64) ::
- ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) ::
- ("htlcMinimum" | millisatoshi) ::
- ("toSelfDelay" | cltvExpiryDelta) ::
- ("maxAcceptedHtlcs" | uint16) ::
- ("fundingPubKey" | publicKey) ::
- ("revocationBasepoint" | publicKey) ::
- ("paymentBasepoint" | publicKey) ::
- ("delayedPaymentBasepoint" | publicKey) ::
- ("htlcBasepoint" | publicKey) ::
- ("features" | combinedFeaturesCodec) ::
- ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[ChannelTypes0.RemoteParams]
-
- def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList)
-
- val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(bool8)
- .typecase(true, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc])
- .typecase(false, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc])
-
- val commitmentSpecCodec: Codec[CommitmentSpec] = (
- ("htlcs" | setCodec(htlcCodec)) ::
- ("feeratePerKw" | feeratePerKw) ::
- ("toLocal" | millisatoshi) ::
- ("toRemote" | millisatoshi)).as[CommitmentSpec]
-
- val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))
-
- val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))
-
- val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
-
- val inputInfoCodec: Codec[InputInfo] = (
- ("outPoint" | outPointCodec) ::
- ("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).map {
- case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut)
- }.decodeOnly
-
- val outputInfoCodec: Codec[Long] = (
- ("index" | uint32) ::
- ("amount" | satoshi) ::
- ("scriptPubKey" | lengthDelimited(bytes))).map {
- case index :: _ :: _ :: HNil => index
- }.decodeOnly
-
- private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0))
- private val missingPaymentHash: Codec[ByteVector32] = provide(ByteVector32.Zeroes)
- private val missingToSelfDelay: Codec[CltvExpiryDelta] = provide(CltvExpiryDelta(0))
- // Those fields have been added to our transactions after we stopped storing them in our channel data, so they're safe to ignore.
- private val unusedCommitmentFormat: Codec[CommitmentFormat] = provide(DefaultCommitmentFormat)
- private val dummyPrivateKey = PrivateKey(hex"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1")
- private val dummyPublicKey = dummyPrivateKey.publicKey
- private val unusedRemoteCommitKeys: Codec[RemoteCommitmentKeys] = provide(RemoteCommitmentKeys(Right(dummyPrivateKey), dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedLocalCommitKeys: Codec[LocalCommitmentKeys] = provide(LocalCommitmentKeys(dummyPrivateKey, dummyPublicKey, dummyPublicKey, dummyPrivateKey, dummyPublicKey, dummyPublicKey))
- private val unusedFundingKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevocationKey: Codec[PrivateKey] = provide(dummyPrivateKey)
- private val unusedRevokedRedeemInfo: Codec[RedeemInfo] = provide(RedeemInfo.P2wsh(Nil))
-
- val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]
- val htlcSuccessTxCodec: Codec[UnsignedHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | cltvExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcSuccessTx]
- val htlcTimeoutTxCodec: Codec[UnsignedHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | cltvExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcTimeoutTx]
- private val htlcSuccessTxNoConfirmCodec: Codec[UnsignedHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcSuccessTx]
- private val htlcTimeoutTxNoConfirmCodec: Codec[UnsignedHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[UnsignedHtlcTimeoutTx]
- val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[HtlcDelayedTx]
- private val legacyClaimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx]
- val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | cltvExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx]
- val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | cltvExpiry) :: unusedCommitmentFormat).as[ClaimHtlcTimeoutTx]
- private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcSuccessTx]
- private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcId" | uint64overflow) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[ClaimHtlcTimeoutTx]
- val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimLocalDelayedOutputTx]
- val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimP2WPKHOutputTx]
- val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (unusedRemoteCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimRemoteDelayedOutputTx]
- val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[MainPenaltyTx]
- val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: unusedRevokedRedeemInfo :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | missingPaymentHash) :: ("htlcExpiry" | missingHtlcExpiry) :: unusedCommitmentFormat).as[HtlcPenaltyTx]
- val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (unusedRemoteCommitKeys :: unusedRevocationKey :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("toSelfDelay" | missingToSelfDelay) :: unusedCommitmentFormat).as[ClaimHtlcDelayedOutputPenaltyTx]
- val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- private val claimLocalAnchorOutputTxWithConfirmationTargetCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | ignore(32)) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- // We previously created an unused transaction spending the remote anchor (after the 16-blocks delay).
- val unusedRemoteAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = (unusedFundingKey :: unusedLocalCommitKeys :: ("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: unusedCommitmentFormat).as[ClaimLocalAnchorTx]
- val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]
-
- val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8)
- .typecase(0x01, claimP2WPKHOutputTxCodec)
- .typecase(0x02, claimRemoteDelayedOutputTxCodec)
-
- val claimAnchorOutputTxCodec: Codec[ClaimLocalAnchorTx] = discriminated[ClaimLocalAnchorTx].by(uint8)
- // Important: order matters!
- .typecase(0x12, claimLocalAnchorOutputTxCodec)
- .typecase(0x11, claimLocalAnchorOutputTxWithConfirmationTargetCodec)
- .typecase(0x01, claimLocalAnchorOutputTxNoConfirmCodec)
- .typecase(0x02, unusedRemoteAnchorOutputTxCodec)
-
- val htlcTxCodec: Codec[UnsignedHtlcTx] = discriminated[UnsignedHtlcTx].by(uint8)
- // Important: order matters!
- .typecase(0x11, htlcSuccessTxCodec)
- .typecase(0x12, htlcTimeoutTxCodec)
- .typecase(0x01, htlcSuccessTxNoConfirmCodec)
- .typecase(0x02, htlcTimeoutTxNoConfirmCodec)
-
- val claimHtlcTxCodec: Codec[ClaimHtlcTx] = discriminated[ClaimHtlcTx].by(uint8)
- // Important: order matters!
- .typecase(0x22, claimHtlcTimeoutTxCodec)
- .typecase(0x23, claimHtlcSuccessTxCodec)
- .typecase(0x01, legacyClaimHtlcSuccessTxCodec)
- .typecase(0x02, claimHtlcTimeoutTxNoConfirmCodec)
- .typecase(0x03, claimHtlcSuccessTxNoConfirmCodec)
-
- val htlcTxsAndRemoteSigsCodec: Codec[ChannelTypes3.HtlcTxAndRemoteSig] = (
- ("txinfo" | htlcTxCodec) ::
- ("remoteSig" | bytes64)).as[ChannelTypes3.HtlcTxAndRemoteSig]
-
- val commitTxAndRemoteSigCodec: Codec[ChannelTypes3.CommitTxAndRemoteSig] = (
- ("commitTx" | commitTxCodec) ::
- ("remoteSig" | bytes64.as[ChannelSpendSignature.IndividualSignature])).as[ChannelTypes3.CommitTxAndRemoteSig]
-
- val localCommitCodec: Codec[ChannelTypes3.LocalCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("commitTxAndRemoteSig" | commitTxAndRemoteSigCodec) ::
- ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[ChannelTypes3.LocalCommit]
-
- val remoteCommitCodec: Codec[RemoteCommit] = (
- ("index" | uint64overflow) ::
- ("spec" | commitmentSpecCodec) ::
- ("txid" | txId) ::
- ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit]
-
- val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g))
-
- val localChangesCodec: Codec[LocalChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges]
-
- val remoteChangesCodec: Codec[RemoteChanges] = (
- ("proposed" | listOfN(uint16, updateMessageCodec)) ::
- ("acked" | listOfN(uint16, updateMessageCodec)) ::
- ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges]
-
- val waitingForRevocationCodec: Codec[ChannelTypes3.WaitingForRevocation] = (
- ("nextRemoteCommit" | remoteCommitCodec) ::
- ("sent" | lengthDelimited(commitSigCodec)) ::
- ("sentAfterLocalCommitIndex" | uint64overflow) ::
- ("reSignAsap" | ignore(8))).as[ChannelTypes3.WaitingForRevocation]
-
- val upstreamLocalCodec: Codec[Upstream.Local] = ("id" | uuid).as[Upstream.Local]
-
- val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | millisatoshi) ::
- ("amountOut" | ignore(64))).as[Upstream.Cold.Channel]
-
- val upstreamChannelWithoutAmountCodec: Codec[Upstream.Cold.Channel] = (
- ("originChannelId" | bytes32) ::
- ("originHtlcId" | int64) ::
- ("amountIn" | provide(0 msat))).as[Upstream.Cold.Channel]
-
- val upstreamTrampolineCodec: Codec[Upstream.Cold.Trampoline] = listOfN(uint16, upstreamChannelWithoutAmountCodec).as[Upstream.Cold.Trampoline]
-
- val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16)
- .typecase(0x02, upstreamChannelCodec)
- .typecase(0x03, upstreamLocalCodec)
- .typecase(0x04, upstreamTrampolineCodec)
-
- val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin](
- upstream => Origin.Cold(upstream),
- {
- case Origin.Hot(_, upstream) => Upstream.Cold(upstream)
- case Origin.Cold(upstream) => upstream
- }
- )
-
- def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList)
-
- val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec)
-
- val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec)
-
- val commitmentsCodec: Codec[Commitments] = (
- ("channelId" | bytes32) ::
- ("channelConfig" | channelConfigCodec) ::
- (("channelFeatures" | channelFeaturesCodec) >>:~ { channelFeatures =>
- ("localParams" | localParamsCodec(channelFeatures)) ::
- ("remoteParams" | remoteParamsCodec(channelFeatures)) ::
- ("channelFlags" | channelflags) ::
- ("localCommit" | localCommitCodec) ::
- ("remoteCommit" | remoteCommitCodec) ::
- ("localChanges" | localChangesCodec) ::
- ("remoteChanges" | remoteChangesCodec) ::
- ("localNextHtlcId" | uint64overflow) ::
- ("remoteNextHtlcId" | uint64overflow) ::
- ("originChannels" | originsMapCodec) ::
- ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) ::
- ("commitInput" | inputInfoCodec.map(_ => ()).decodeOnly) ::
- ("fundingTxStatus" | provide(SingleFundedUnconfirmedFundingTx(None)).upcast[LocalFundingStatus]) ::
- ("remoteFundingTxStatus" | provide(RemoteFundingStatus.Locked).upcast[RemoteFundingStatus]) ::
- ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec))
- })).as[ChannelTypes3.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly
-
- /** Once a dual funding tx has been signed, we must remember the associated commitments. */
- case class DualFundingTx(fundingTx: SignedSharedTransaction, commitments: ChannelTypes3.Commitments)
-
- private val dualFundingTxCodec: Codec[DualFundingTx] = fail[DualFundingTx](Err("you have been running dual funding before it was officially released! contact developers"))
-
- val closingFeeratesCodec: Codec[ClosingFeerates] = (
- ("preferred" | feeratePerKw) ::
- ("min" | feeratePerKw) ::
- ("max" | feeratePerKw)).as[ClosingFeerates]
-
- /** If there are closing fees defined we consider ourselves to be the closing initiator. */
- val closeStatusCompatCodec: Codec[Option[CloseStatus]] = optional(bool8, closingFeeratesCodec).map(feerates_opt => Some(CloseStatus.Initiator(feerates_opt))).decodeOnly
-
- val closingTxProposedCodec: Codec[ClosingTxProposed] = (
- ("unsignedTx" | closingTxCodec) ::
- ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed]
-
- val localCommitPublishedCodec: Codec[LocalCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainDelayedOutputTx" | optional(bool8, claimLocalDelayedOutputTxCodec)) ::
- ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) ::
- ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxCodec)) ::
- ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly
-
- val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) ::
- ("claimHtlcTxs" | mapCodec(outPointCodec, optional(bool8, claimHtlcTxCodec))) ::
- ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly
-
- val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = (
- ("commitTx" | txCodec) ::
- ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) ::
- ("mainPenaltyTx" | optional(bool8, mainPenaltyTxCodec)) ::
- ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxCodec)) ::
- ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxCodec)) ::
- ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly
-
- private val shortids: Codec[ShortIdAliases] = (
- ("real_opt" | optional(bool8, realshortchannelid)) ::
- ("localAlias" | discriminated[Alias].by(uint16).typecase(1, alias)) ::
- ("remoteAlias_opt" | optional(bool8, alias))
- ).map {
- case _ :: localAlias :: remoteAlias_opt :: HNil => ShortIdAliases(localAlias, remoteAlias_opt)
- }.decodeOnly
-
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) ::
- ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).map {
- case commitments :: fundingTx :: waitingSince :: deferred :: lastSent :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx))
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments1, waitingSince, deferred, lastSent)
- }.decodeOnly
-
- val DATA_WAIT_FOR_CHANNEL_READY_01_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("lastSent" | lengthDelimited(channelReadyCodec))).map {
- case commitments :: shortChannelId :: _ :: HNil =>
- DATA_WAIT_FOR_CHANNEL_READY(commitments, aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None))
- }.decodeOnly
-
- val DATA_WAIT_FOR_CHANNEL_READY_0a_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortIds" | shortids)).as[DATA_WAIT_FOR_CHANNEL_READY]
-
- val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_0b_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = fail(Err("you have been running dual funding before it was officially released! contact developers"))
-
- val DATA_WAIT_FOR_DUAL_FUNDING_READY_0c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = (
- ("commitments" | commitmentsCodec) ::
- ("shortIds" | shortids)).as[DATA_WAIT_FOR_DUAL_FUNDING_READY]
- .decodeOnly
-
- val DATA_NORMAL_02_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool8) ::
- ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) ::
- ("channelUpdate" | lengthDelimited(channelUpdateCodec)) ::
- ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("closeStatus" | provide(Option.empty[CloseStatus]))).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus)
- }.decodeOnly
-
- val DATA_NORMAL_07_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortChannelId" | realshortchannelid) ::
- ("buried" | bool8) ::
- ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) ::
- ("channelUpdate" | lengthDelimited(channelUpdateCodec)) ::
- ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("closeStatus" | closeStatusCompatCodec)).map {
- case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil =>
- val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None)
- DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus)
- }.decodeOnly
-
- val DATA_NORMAL_09_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
- ("shortids" | shortids) ::
- ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) ::
- ("channelUpdate" | lengthDelimited(channelUpdateCodec)) ::
- ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
- ("closeStatus" | closeStatusCompatCodec) ::
- ("spliceStatus" | provide[SpliceStatus](SpliceStatus.NoSplice))).map {
- case commitments :: shortIds :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil =>
- DATA_NORMAL(commitments, shortIds, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus)
- }.decodeOnly
-
- val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closeStatus" | provide[CloseStatus](CloseStatus.Initiator(None)))).as[DATA_SHUTDOWN]
-
- val DATA_SHUTDOWN_08_Codec: Codec[DATA_SHUTDOWN] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closingFeerates" | optional(bool8, closingFeeratesCodec).map[CloseStatus](feerates_opt => CloseStatus.Initiator(feerates_opt)).decodeOnly)).as[DATA_SHUTDOWN]
-
- val DATA_NEGOTIATING_04_Codec: Codec[DATA_NEGOTIATING] = (
- ("commitments" | commitmentsCodec) ::
- ("localShutdown" | lengthDelimited(shutdownCodec)) ::
- ("remoteShutdown" | lengthDelimited(shutdownCodec)) ::
- ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) ::
- ("bestUnpublishedClosingTx_opt" | optional(bool8, closingTxCodec))).as[DATA_NEGOTIATING]
-
- val DATA_CLOSING_05_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, txCodec)) ::
- ("waitingSince" | int64.as[BlockHeight]) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt))
- DATA_CLOSING(commitments1, waitingSince, commitments1.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val unconfirmedFundingTxCodec: Codec[UnconfirmedFundingTx] = discriminated[UnconfirmedFundingTx].by(uint8)
- .typecase(0x01, txCodec.map(tx => SingleFundedUnconfirmedFundingTx(Some(tx))).decodeOnly)
- .typecase(0x02, fail[DualFundedUnconfirmedFundingTx](Err("you have been running dual funding before it was officially released! contact developers")))
-
- val DATA_CLOSING_0d_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
- ("fundingTx_opt" | optional(bool8, unconfirmedFundingTxCodec)) ::
- ("waitingSince" | blockHeight) ::
- ("alternativeCommitments" | listOfN(uint16, dualFundingTxCodec)) ::
- ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) ::
- ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) ::
- ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) ::
- ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) ::
- ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map {
- case commitments :: fundingTx_opt :: waitingSince :: _ :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil =>
- val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt.flatMap(_.signedTx_opt)))
- DATA_CLOSING(commitments1, waitingSince, commitments.localChannelParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished)
- }.decodeOnly
-
- val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_06_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = (
- ("commitments" | commitmentsCodec) ::
- ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT]
- }
-
- // Order matters!
- val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16)
- .typecase(0x0d, Codecs.DATA_CLOSING_0d_Codec)
- .typecase(0x0c, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_READY_0c_Codec)
- .typecase(0x0b, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_0b_Codec)
- .typecase(0x0a, Codecs.DATA_WAIT_FOR_CHANNEL_READY_0a_Codec)
- .typecase(0x09, Codecs.DATA_NORMAL_09_Codec)
- .typecase(0x08, Codecs.DATA_SHUTDOWN_08_Codec)
- .typecase(0x07, Codecs.DATA_NORMAL_07_Codec)
- .typecase(0x06, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_06_Codec)
- .typecase(0x05, Codecs.DATA_CLOSING_05_Codec)
- .typecase(0x04, Codecs.DATA_NEGOTIATING_04_Codec)
- .typecase(0x03, Codecs.DATA_SHUTDOWN_03_Codec)
- .typecase(0x02, Codecs.DATA_NORMAL_02_Codec)
- .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_01_Codec)
- .typecase(0x00, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec)
-
-}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala
deleted file mode 100644
index 5c8ee95..0000000
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2023 ACINQ SAS
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package fr.acinq.eclair.wire.internal.channel.version3
-
-import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64}
-import fr.acinq.eclair.channel._
-import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession
-import fr.acinq.eclair.crypto.ShaChain
-import fr.acinq.eclair.transactions.CommitmentSpec
-import fr.acinq.eclair.transactions.Transactions._
-import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0
-import fr.acinq.eclair.wire.protocol.CommitSig
-import fr.acinq.eclair.{Features, InitFeature, PermanentChannelFeature, channel}
-
-private[channel] object ChannelTypes3 {
-
- // We previously stored channel type features inside our channel features.
- case class ChannelFeatures(features: Set[InitFeature]) {
- val paysDirectlyToWallet: Boolean = features.contains(Features.StaticRemoteKey) && !features.contains(Features.AnchorOutputs) && !features.contains(Features.AnchorOutputsZeroFeeHtlcTx)
-
- /** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */
- val commitmentFormat: CommitmentFormat = if (features.contains(Features.AnchorOutputs)) {
- UnsafeLegacyAnchorOutputsCommitmentFormat
- } else if (features.contains(Features.AnchorOutputsZeroFeeHtlcTx)) {
- ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
- } else {
- DefaultCommitmentFormat
- }
-
- def migrate(): channel.ChannelFeatures = channel.ChannelFeatures(features.collect { case f: PermanentChannelFeature => f })
- }
-
- case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long)
-
- case class HtlcTxAndRemoteSig(htlcTx: UnsignedHtlcTx, remoteSig: ByteVector64)
-
- case class CommitTxAndRemoteSig(commitTx: CommitTx, remoteSig: ChannelSpendSignature.IndividualSignature)
-
- // Before version 4, we stored the unsigned commit tx and htlc txs in our local commit.
- // We changed that to only store the remote signatures and re-compute transactions on-the-fly when force-closing.
- case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndReWhy this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.