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:
parent
6c81f95644
commit
928d47cd84
8 changed files with 250 additions and 0 deletions
|
@ -45,6 +45,7 @@ class BitgoFeeProvider(chainHash: ByteVector32, readTimeOut: Duration)(implicit
|
|||
.send()
|
||||
feeRanges = parseFeeRanges(res.unsafeBody)
|
||||
} yield extractFeerates(feeRanges)
|
||||
|
||||
}
|
||||
|
||||
object BitgoFeeProvider {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -44,6 +44,7 @@ class EarnDotComFeeProvider(readTimeOut: Duration)(implicit http: SttpBackend[Fu
|
|||
.send()
|
||||
feeRanges = parseFeeRanges(json.unsafeBody)
|
||||
} yield extractFeerates(feeRanges)
|
||||
|
||||
}
|
||||
|
||||
object EarnDotComFeeProvider {
|
||||
|
|
|
@ -34,6 +34,7 @@ class SmoothFeeProvider(provider: FeeProvider, windowSize: Int)(implicit ec: Exe
|
|||
_ = append(rate)
|
||||
} yield SmoothFeeProvider.smooth(queue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object SmoothFeeProvider {
|
||||
|
|
|
@ -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]
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue