1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 06:35:11 +01:00

Add a fee provider saving feerates to database (#1450)

This provider will save the feerates retrieved by another provider to 
database.

This feature can be used to retrieve the last used feerates when starting 
the node, which will save time. This can have a significant effect on nodes 
running with a slow connection (e.g. mobile devices).

Note that this commit does not affect the current setup and does not
actually create the database, the feature must be implemented separately.

Fixes #1447
This commit is contained in:
Dominique 2020-06-22 18:31:45 +02:00 committed by GitHub
parent 6c81f95644
commit 928d47cd84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 250 additions and 0 deletions

View file

@ -45,6 +45,7 @@ class BitgoFeeProvider(chainHash: ByteVector32, readTimeOut: Duration)(implicit
.send()
feeRanges = parseFeeRanges(res.unsafeBody)
} yield extractFeerates(feeRanges)
}
object BitgoFeeProvider {

View file

@ -0,0 +1,33 @@
/*
* Copyright 2020 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.blockchain.fee
import fr.acinq.eclair.db.FeeratesDb
import scala.concurrent.{ExecutionContext, Future}
class DbFeeProvider(db: FeeratesDb, provider: FeeProvider)(implicit ec: ExecutionContext) extends FeeProvider {
/** This method retrieves feerates from the provider, and store results in the database */
override def getFeerates: Future[FeeratesPerKB] =
provider.getFeerates map { feerates =>
db.addOrUpdateFeerates(feerates)
feerates
}
}

View file

@ -44,6 +44,7 @@ class EarnDotComFeeProvider(readTimeOut: Duration)(implicit http: SttpBackend[Fu
.send()
feeRanges = parseFeeRanges(json.unsafeBody)
} yield extractFeerates(feeRanges)
}
object EarnDotComFeeProvider {

View file

@ -34,6 +34,7 @@ class SmoothFeeProvider(provider: FeeProvider, windowSize: Int)(implicit ec: Exe
_ = append(rate)
} yield SmoothFeeProvider.smooth(queue)
}
}
object SmoothFeeProvider {

View file

@ -0,0 +1,34 @@
/*
* Copyright 2020 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.db
import java.io.Closeable
import fr.acinq.eclair.blockchain.fee.FeeratesPerKB
/**
* This database stores the fee rates retrieved by a [[fr.acinq.eclair.blockchain.fee.FeeProvider]].
*/
trait FeeratesDb extends Closeable {
/** Insert or update the feerates into the feerates database. */
def addOrUpdateFeerates(feeratesPerKB: FeeratesPerKB): Unit
/** Return the (optional) feerates from the feerates database. */
def getFeerates(): Option[FeeratesPerKB]
}

View file

@ -0,0 +1,91 @@
/*
* Copyright 2020 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.db.sqlite
import java.sql.Connection
import fr.acinq.eclair.blockchain.fee.FeeratesPerKB
import fr.acinq.eclair.db.FeeratesDb
class SqliteFeeratesDb(sqlite: Connection) extends FeeratesDb {
import SqliteUtils._
val DB_NAME = "feerates"
val CURRENT_VERSION = 1
using(sqlite.createStatement(), inTransaction = true) { statement =>
getVersion(statement, DB_NAME, CURRENT_VERSION) match {
case CURRENT_VERSION =>
// Create feerates table. Rates are in kb.
statement.executeUpdate(
"""
|CREATE TABLE IF NOT EXISTS feerates_per_kb (
|rate_block_1 INTEGER NOT NULL, rate_blocks_2 INTEGER NOT NULL, rate_blocks_6 INTEGER NOT NULL, rate_blocks_12 INTEGER NOT NULL, rate_blocks_36 INTEGER NOT NULL, rate_blocks_72 INTEGER NOT NULL, rate_blocks_144 INTEGER NOT NULL,
|timestamp INTEGER NOT NULL)""".stripMargin)
case unknownVersion => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
}
}
override def addOrUpdateFeerates(feeratesPerKB: FeeratesPerKB): Unit = {
using(sqlite.prepareStatement("UPDATE feerates_per_kb SET rate_block_1=?, rate_blocks_2=?, rate_blocks_6=?, rate_blocks_12=?, rate_blocks_36=?, rate_blocks_72=?, rate_blocks_144=?, timestamp=?")) { update =>
update.setLong(1, feeratesPerKB.block_1)
update.setLong(2, feeratesPerKB.blocks_2)
update.setLong(3, feeratesPerKB.blocks_6)
update.setLong(4, feeratesPerKB.blocks_12)
update.setLong(5, feeratesPerKB.blocks_36)
update.setLong(6, feeratesPerKB.blocks_72)
update.setLong(7, feeratesPerKB.blocks_144)
update.setLong(8, System.currentTimeMillis())
if (update.executeUpdate() == 0) {
using(sqlite.prepareStatement("INSERT INTO feerates_per_kb VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) { insert =>
insert.setLong(1, feeratesPerKB.block_1)
insert.setLong(2, feeratesPerKB.blocks_2)
insert.setLong(3, feeratesPerKB.blocks_6)
insert.setLong(4, feeratesPerKB.blocks_12)
insert.setLong(5, feeratesPerKB.blocks_36)
insert.setLong(6, feeratesPerKB.blocks_72)
insert.setLong(7, feeratesPerKB.blocks_144)
insert.setLong(8, System.currentTimeMillis())
insert.executeUpdate()
}
}
}
}
override def getFeerates(): Option[FeeratesPerKB] = {
using(sqlite.prepareStatement("SELECT rate_block_1, rate_blocks_2, rate_blocks_6, rate_blocks_12, rate_blocks_36, rate_blocks_72, rate_blocks_144 FROM feerates_per_kb")) { statement =>
val rs = statement.executeQuery()
if (rs.next()) {
Some(FeeratesPerKB(
block_1 = rs.getLong("rate_block_1"),
blocks_2 = rs.getLong("rate_blocks_2"),
blocks_6 = rs.getLong("rate_blocks_6"),
blocks_12 = rs.getLong("rate_blocks_12"),
blocks_36 = rs.getLong("rate_blocks_36"),
blocks_72 = rs.getLong("rate_blocks_72"),
blocks_144 = rs.getLong("rate_blocks_144")))
} else {
None
}
}
}
// used by mobile apps
override def close(): Unit = sqlite.close()
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 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.blockchain.fee
import akka.util.Timeout
import fr.acinq.eclair.TestConstants
import fr.acinq.eclair.db.sqlite.SqliteFeeratesDb
import org.scalatest.funsuite.AnyFunSuite
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
class DbFeeProviderSpec extends AnyFunSuite {
val feerates1: FeeratesPerKB = FeeratesPerKB(100, 200, 300, 400, 500, 600, 700)
test("db fee provider saves feerates in database") {
val sqlite = TestConstants.sqliteInMemory()
val db = new SqliteFeeratesDb(sqlite)
val provider = new DbFeeProvider(db, new ConstantFeeProvider(feerates1))
assert(db.getFeerates().isEmpty)
assert(Await.result(provider.getFeerates, Timeout(30 seconds).duration) == feerates1)
assert(db.getFeerates().get == feerates1)
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2020 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.db
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.fee.FeeratesPerKB
import fr.acinq.eclair.db.sqlite.SqliteFeeratesDb
import org.scalatest.funsuite.AnyFunSuite
class SqliteFeeratesDbSpec extends AnyFunSuite {
test("init sqlite 2 times in a row") {
val sqlite = TestConstants.sqliteInMemory()
val db1 = new SqliteFeeratesDb(sqlite)
val db2 = new SqliteFeeratesDb(sqlite)
}
test("add/get feerates") {
val sqlite = TestConstants.sqliteInMemory()
val db = new SqliteFeeratesDb(sqlite)
val feerate = FeeratesPerKB(
block_1 = 150000,
blocks_2 = 120000,
blocks_6 = 100000,
blocks_12 = 90000,
blocks_36 = 70000,
blocks_72 = 50000,
blocks_144 = 20000)
db.addOrUpdateFeerates(feerate)
assert(db.getFeerates().get == feerate)
}
}