Skip to content

Commit

Permalink
Add a fee provider saving feerates to database (#1450)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dpad85 authored Jun 22, 2020
1 parent 6c81f95 commit 928d47c
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class BitgoFeeProvider(chainHash: ByteVector32, readTimeOut: Duration)(implicit
.send()
feeRanges = parseFeeRanges(res.unsafeBody)
} yield extractFeerates(feeRanges)

}

object BitgoFeeProvider {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class EarnDotComFeeProvider(readTimeOut: Duration)(implicit http: SttpBackend[Fu
.send()
feeRanges = parseFeeRanges(json.unsafeBody)
} yield extractFeerates(feeRanges)

}

object EarnDotComFeeProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SmoothFeeProvider(provider: FeeProvider, windowSize: Int)(implicit ec: Exe
_ = append(rate)
} yield SmoothFeeProvider.smooth(queue)
}

}

object SmoothFeeProvider {
Expand Down
34 changes: 34 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/db/FeeratesDb.scala
Original file line number Diff line number Diff line change
@@ -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]

}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 928d47c

Please sign in to comment.