Get FilterSync test working with cached bitcoind in chainTest project (#2952)

* Get FilterSync test working with cached bitcoind in chainTest project

* Small refactor to be DRY

* Fix docs

* Refactor ChainSyncTest to use 1 cached bitcoind, as a by product add ChainWithBitcoindNewestCachedUnitTest

* Remove unecessary mixin trait

* Fix missing ChainWithBitcoindNewestCachedUnitTest.afterAll()

* Reduce thread pool size for akka's internal dispatcher in unit tests from 2 -> 1. Same with the blocking dispatcher

* Add comment
This commit is contained in:
Chris Stewart 2021-04-26 07:41:30 -05:00 committed by GitHub
parent 0cad0edaaf
commit a27d4acd9f
12 changed files with 325 additions and 104 deletions

View File

@ -3,23 +3,20 @@ package org.bitcoins.chain.blockchain.sync
import org.bitcoins.chain.blockchain.ChainHandler import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.core.api.chain.ChainApi import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.crypto.DoubleSha256DigestBE import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.testkit.chain.fixture.BitcoindChainHandlerViaRpc import org.bitcoins.testkit.chain.SyncUtil
import org.bitcoins.testkit.chain.{ChainDbUnitTest, SyncUtil} import org.bitcoins.testkit.chain.fixture.{
import org.scalatest.FutureOutcome BitcoindBaseVersionChainHandlerViaRpc,
ChainWithBitcoindNewestCachedUnitTest
}
import scala.concurrent.Future import scala.concurrent.Future
class ChainSyncTest extends ChainDbUnitTest { class ChainSyncTest extends ChainWithBitcoindNewestCachedUnitTest {
override type FixtureParam = BitcoindChainHandlerViaRpc
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
withBitcoindChainHandlerViaRpc(test)
}
behavior of "ChainSync" behavior of "ChainSync"
it must "sync our chain handler when it is not synced with bitcoind" in { it must "sync our chain handler when it is not synced with bitcoind" in {
bitcoindWithChainHandler: BitcoindChainHandlerViaRpc => bitcoindWithChainHandler: BitcoindBaseVersionChainHandlerViaRpc =>
val bitcoind = bitcoindWithChainHandler.bitcoindRpc val bitcoind = bitcoindWithChainHandler.bitcoindRpc
val chainHandler = bitcoindWithChainHandler.chainHandler val chainHandler = bitcoindWithChainHandler.chainHandler
//first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions //first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions
@ -36,14 +33,17 @@ class ChainSyncTest extends ChainDbUnitTest {
getBestBlockHashFunc = getBestBlockHashFunc) getBestBlockHashFunc = getBestBlockHashFunc)
} }
newChainHandlerF.flatMap { chainHandler => for {
chainHandler.getBlockCount().map(count => assert(count == 1)) chainHandler <- newChainHandlerF
count <- chainHandler.getBlockCount()
bitcoindCount <- bitcoind.getBlockCount
} yield {
assert(bitcoindCount == count)
} }
} }
it must "not fail when syncing a chain handler that is synced with it's external data source" in { it must "not fail when syncing a chain handler that is synced with it's external data source" in {
bitcoindWithChainHandler: BitcoindChainHandlerViaRpc => bitcoindWithChainHandler: BitcoindBaseVersionChainHandlerViaRpc =>
val bitcoind = bitcoindWithChainHandler.bitcoindRpc val bitcoind = bitcoindWithChainHandler.bitcoindRpc
val chainHandler = bitcoindWithChainHandler.chainHandler val chainHandler = bitcoindWithChainHandler.chainHandler
//first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions //first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions
@ -61,9 +61,18 @@ class ChainSyncTest extends ChainDbUnitTest {
getBlockHeaderFunc = getBlockHeaderFunc, getBlockHeaderFunc = getBlockHeaderFunc,
getBestBlockHashFunc = getBestBlockHashFunc) getBestBlockHashFunc = getBestBlockHashFunc)
newChainHandlerF.flatMap { chainHandler => val newChainHandler2F = for {
chainHandler.getBlockCount().map(count => assert(count == 0)) newChainHandler <- newChainHandlerF
} //sync it again to make sure we don't fail
newChainHandler2 <- ChainSync.sync(
chainHandler = newChainHandler.asInstanceOf[ChainHandler],
getBlockHeaderFunc = getBlockHeaderFunc,
getBestBlockHashFunc = getBestBlockHashFunc)
bitcoinSCount <- newChainHandler2.getBlockCount()
bitcoindCount <- bitcoind.getBlockCount
} yield assert(bitcoinSCount == bitcoindCount)
newChainHandler2F
} }
it must "be able to call sync() twice and not fail when nothing has happened" in { it must "be able to call sync() twice and not fail when nothing has happened" in {
@ -87,13 +96,15 @@ class ChainSyncTest extends ChainDbUnitTest {
} }
val assertion1F = for { val assertion1F = for {
hashes <- generate1F _ <- generate1F
chainApiSync1 <- sync1F chainApiSync1 <- sync1F
count <- chainApiSync1.getBlockCount() count <- chainApiSync1.getBlockCount()
bestHash <- chainApiSync1.getBestBlockHash() bestHash <- chainApiSync1.getBestBlockHash()
bitcoindBlockCount <- bitcoind.getBlockCount
bitcoindBestBlockHash <- bitcoind.getBestBlockHash
} yield { } yield {
assert(count == 1) assert(count == bitcoindBlockCount)
assert(bestHash == hashes.head) assert(bestHash == bitcoindBestBlockHash)
} }
//let's call sync again and make sure nothing bad happens //let's call sync again and make sure nothing bad happens
@ -106,11 +117,13 @@ class ChainSyncTest extends ChainDbUnitTest {
getBlockHeaderFunc = getBlockHeaderFunc, getBlockHeaderFunc = getBlockHeaderFunc,
getBestBlockHashFunc = getBestBlockHashFunc) getBestBlockHashFunc = getBestBlockHashFunc)
count <- chainApiSync2.getBlockCount() count <- chainApiSync2.getBlockCount()
hashes <- generate1F _ <- generate1F
bestHash <- chainApiSync2.getBestBlockHash() bestHash <- chainApiSync2.getBestBlockHash()
bitcoindBlockCount <- bitcoind.getBlockCount
bitcoindBestBlockHash <- bitcoind.getBestBlockHash
} yield { } yield {
assert(count == 1) assert(count == bitcoindBlockCount)
assert(bestHash == hashes.head) assert(bestHash == bitcoindBestBlockHash)
} }
sync2F sync2F

View File

@ -4,19 +4,15 @@ import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.core.api.chain.ChainApi import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.core.gcs.FilterType import org.bitcoins.core.gcs.FilterType
import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.testkit.chain.fixture.BitcoindV19ChainHandler import org.bitcoins.testkit.chain.SyncUtil
import org.bitcoins.testkit.chain.{ChainDbUnitTest, SyncUtil} import org.bitcoins.testkit.chain.fixture.{
import org.scalatest.FutureOutcome BitcoindV19ChainHandler,
ChainWithBitcoindV19CachedUnitTest
}
import scala.concurrent.Future import scala.concurrent.Future
class FilterSyncTest extends ChainDbUnitTest { class FilterSyncTest extends ChainWithBitcoindV19CachedUnitTest {
override type FixtureParam = BitcoindV19ChainHandler
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
withBitcoindV19ChainHandlerViaRpc(test)
}
behavior of "FilterSync" behavior of "FilterSync"
@ -25,19 +21,25 @@ class FilterSyncTest extends ChainDbUnitTest {
val initFilterCountF = chainHandler.getFilterCount() val initFilterCountF = chainHandler.getFilterCount()
val initFilterHeaderCountF = chainHandler.getFilterHeaderCount() val initFilterHeaderCountF = chainHandler.getFilterHeaderCount()
val bitcoindFilterCountF = bitcoind.getFilterCount()
val initAssertionsF = for { val initAssertionsF = for {
initFilterCount <- initFilterCountF initFilterCount <- initFilterCountF
initFilterHeaderCount <- initFilterHeaderCountF initFilterHeaderCount <- initFilterHeaderCountF
bitcoindFilterCount <- bitcoindFilterCountF
} yield { } yield {
assert(initFilterCount == 0) assert(initFilterCount == bitcoindFilterCount)
assert(initFilterHeaderCount == 0) assert(initFilterHeaderCount == bitcoindFilterCount)
} }
val generated1BlockF = for { val generated1BlockF = for {
_ <- initAssertionsF _ <- initAssertionsF
addr <- bitcoind.getNewAddress addr <- bitcoind.getNewAddress
hashes <- bitcoind.generateToAddress(1, addr) hashes <- bitcoind.generateToAddress(1, addr)
} yield hashes } yield {
hashes
}
val syncedF = generated1BlockF.flatMap { _ => val syncedF = generated1BlockF.flatMap { _ =>
syncHelper(fixture) syncHelper(fixture)
@ -46,9 +48,10 @@ class FilterSyncTest extends ChainDbUnitTest {
for { for {
syncedChainApi <- syncedF syncedChainApi <- syncedF
filterHeaderCount <- syncedChainApi.getFilterHeaderCount() filterHeaderCount <- syncedChainApi.getFilterHeaderCount()
_ = assert(filterHeaderCount == 1) bitcoindFilterCount <- bitcoind.getFilterCount()
_ = assert(filterHeaderCount == bitcoindFilterCount)
filterCount <- syncedChainApi.getFilterCount() filterCount <- syncedChainApi.getFilterCount()
} yield assert(filterCount == 1) } yield assert(filterCount == bitcoindFilterCount)
} }
it must "sync a bunch of filter headers from an external data source" in { it must "sync a bunch of filter headers from an external data source" in {
@ -67,10 +70,11 @@ class FilterSyncTest extends ChainDbUnitTest {
for { for {
syncedChainApi <- syncedF syncedChainApi <- syncedF
bitcoindFilterCount <- bitcoind.getFilterCount()
filterHeaderCount <- syncedChainApi.getFilterHeaderCount() filterHeaderCount <- syncedChainApi.getFilterHeaderCount()
_ = assert(filterHeaderCount == numBlocks) _ = assert(filterHeaderCount == bitcoindFilterCount)
filterCount <- syncedChainApi.getFilterCount() filterCount <- syncedChainApi.getFilterCount()
} yield assert(filterCount == numBlocks) } yield assert(filterCount == bitcoindFilterCount)
} }
it must "be able to call filterSync() and not fail when nothing has happened" in { it must "be able to call filterSync() and not fail when nothing has happened" in {
@ -93,10 +97,11 @@ class FilterSyncTest extends ChainDbUnitTest {
for { for {
syncedChainApi <- sync2F syncedChainApi <- sync2F
bitcoindFilterCount <- bitcoind.getFilterCount()
filterHeaderCount <- syncedChainApi.getFilterHeaderCount() filterHeaderCount <- syncedChainApi.getFilterHeaderCount()
_ = assert(filterHeaderCount == 1) _ = assert(filterHeaderCount == bitcoindFilterCount)
filterCount <- syncedChainApi.getFilterCount() filterCount <- syncedChainApi.getFilterCount()
} yield assert(filterCount == 1) } yield assert(filterCount == bitcoindFilterCount)
} }
private def syncHelper( private def syncHelper(
@ -111,10 +116,10 @@ class FilterSyncTest extends ChainDbUnitTest {
SyncUtil.getFilterFunc(bitcoind, filterType) SyncUtil.getFilterFunc(bitcoind, filterType)
//first sync the chain //first sync the chain
val syncedHeadersF = ChainSync.sync(chainHandler = chainHandler, val syncedHeadersF: Future[ChainApi] = ChainSync.sync(
getBlockHeaderFunc = getBlockHeaderFunc, chainHandler = chainHandler,
getBestBlockHashFunc = getBlockHeaderFunc = getBlockHeaderFunc,
getBestBlockHashFunc) getBestBlockHashFunc = getBestBlockHashFunc)
//now sync filters //now sync filters
syncedHeadersF.flatMap { syncedChainHandler => syncedHeadersF.flatMap { syncedChainHandler =>

View File

@ -68,7 +68,6 @@ abstract class ChainSync extends ChainVerificationLogger {
getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader])(implicit getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader])(implicit
ec: ExecutionContext): Future[ChainApi] = { ec: ExecutionContext): Future[ChainApi] = {
require(tips.nonEmpty, s"Cannot sync without the genesis block") require(tips.nonEmpty, s"Cannot sync without the genesis block")
//we need to walk backwards on the chain until we get to one of our tips //we need to walk backwards on the chain until we get to one of our tips
val tipsBH = tips.map(_.blockHeader) val tipsBH = tips.map(_.blockHeader)

View File

@ -34,7 +34,6 @@ abstract class FilterSync extends ChainVerificationLogger {
batchSize: Int = 25)(implicit batchSize: Int = 25)(implicit
ec: ExecutionContext, ec: ExecutionContext,
chainAppConfig: ChainAppConfig): Future[ChainApi] = { chainAppConfig: ChainAppConfig): Future[ChainApi] = {
val ourBestFilterHeaderOptF = chainApi.getBestFilterHeader() val ourBestFilterHeaderOptF = chainApi.getBestFilterHeader()
val ourBestBlockHeaderF = chainApi.getBestBlockHeader() val ourBestBlockHeaderF = chainApi.getBestBlockHeader()
for { for {
@ -96,7 +95,17 @@ abstract class FilterSync extends ChainVerificationLogger {
missing <- chainApi.getHeadersBetween(from = bestFilterBlockHeader.get, missing <- chainApi.getHeadersBetween(from = bestFilterBlockHeader.get,
to = ourBestHeader) to = ourBestHeader)
} yield { } yield {
missing //getHeaderBetween is inclusive with 'from' parameter,
//we only want the inclusive behavior when we are fetching
//from the genesis block hash, so we can get the genesis filter
//else we need the _next_ header after our bestFilterBlockHeader
if (
bestFilterBlockHeader.get.hashBE == chainAppConfig.chain.genesisHashBE
) {
missing
} else {
missing.tail
}
} }
//because filters can be really large, we don't want to process too many //because filters can be really large, we don't want to process too many

View File

@ -5,7 +5,8 @@ import org.bitcoins.crypto.DoubleSha256DigestBE
/** Represents a [[GolombFilter]] with it's [[org.bitcoins.core.gcs.FilterHeader]] associated with it /** Represents a [[GolombFilter]] with it's [[org.bitcoins.core.gcs.FilterHeader]] associated with it
* This is needed because bitcoin core's 'getblockfilter' rpc returns things in this structure * This is needed because bitcoin core's 'getblockfilter' rpc returns things in this structure
* @see https://developer.bitcoin.org/reference/rpc/getblockfilter.html#argument-2-filtertype
*/ */
case class FilterWithHeaderHash( case class FilterWithHeaderHash(
filter: GolombFilter, filter: GolombFilter,
headerHash: DoubleSha256DigestBE) filterHeaderHash: DoubleSha256DigestBE)

View File

@ -64,7 +64,7 @@ implicit val chainAppConfig = BitcoinSTestAppConfig.getNeutrinoTestConfig().chai
val bitcoindWithChainApiF: Future[BitcoindV19ChainHandler] = { val bitcoindWithChainApiF: Future[BitcoindV19ChainHandler] = {
ChainUnitTest.createBitcoindV19ChainHandler() ChainUnitTest.createBitcoindV19ChainHandler()
} }
val bitcoindF = bitcoindWithChainApiF.map(_.bitcoind) val bitcoindF = bitcoindWithChainApiF.map(_.bitcoindRpc)
val chainApiF = bitcoindWithChainApiF.map(_.chainHandler) val chainApiF = bitcoindWithChainApiF.map(_.chainHandler)
val filterType = FilterType.Basic val filterType = FilterType.Basic

View File

@ -173,6 +173,7 @@ akka {
event-stream=off event-stream=off
} }
# https://doc.akka.io/docs/akka/current/dispatchers.html#classic-dispatchers
default-dispatcher { default-dispatcher {
# The goal here is to reduce the number of threads spun up for test suites. # The goal here is to reduce the number of threads spun up for test suites.
# Since every test suite currently # Since every test suite currently
@ -194,20 +195,20 @@ akka {
executor = "thread-pool-executor" executor = "thread-pool-executor"
throughput = 1 throughput = 1
thread-pool-executor { thread-pool-executor {
fixed-pool-size = 2 fixed-pool-size = 1
} }
} }
internal-dispatcher { internal-dispatcher {
# minimum of one thread for tests # minimum of one thread for tests
parallelism-min = 1 parallelism-min = 1
# maximum of 2 threads for tests # maximum of 1 threads for tests
parallelism-max = 2 parallelism-max = 1
type = "Dispatcher" type = "Dispatcher"
executor = "thread-pool-executor" executor = "thread-pool-executor"
throughput = 1 throughput = 1
thread-pool-executor { thread-pool-executor {
fixed-pool-size = 2 fixed-pool-size = 1
} }
} }
} }

View File

@ -1,11 +1,10 @@
package org.bitcoins.testkit.chain package org.bitcoins.testkit.chain
import java.net.InetSocketAddress
import akka.actor.ActorSystem import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import org.bitcoins.chain.ChainVerificationLogger import org.bitcoins.chain.ChainVerificationLogger
import org.bitcoins.chain.blockchain.{ChainHandler, ChainHandlerCached}
import org.bitcoins.chain.blockchain.sync.ChainSync import org.bitcoins.chain.blockchain.sync.ChainSync
import org.bitcoins.chain.blockchain.{ChainHandler, ChainHandlerCached}
import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models._ import org.bitcoins.chain.models._
import org.bitcoins.chain.pow.Pow import org.bitcoins.chain.pow.Pow
@ -33,9 +32,10 @@ import org.bitcoins.zmq.ZMQSubscriber
import org.scalatest._ import org.scalatest._
import play.api.libs.json.{JsError, JsSuccess, Json} import play.api.libs.json.{JsError, JsSuccess, Json}
import java.net.InetSocketAddress
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
trait ChainUnitTest trait ChainUnitTest
extends BitcoinSFixture extends BitcoinSFixture
@ -271,7 +271,7 @@ trait ChainUnitTest
bitcoindChainHandler: BitcoindChainHandlerViaZmq): Future[Unit] = { bitcoindChainHandler: BitcoindChainHandlerViaZmq): Future[Unit] = {
//piggy back off of rpc destructor //piggy back off of rpc destructor
val rpc = chain.fixture.BitcoindChainHandlerViaRpc( val rpc = chain.fixture.BitcoindBaseVersionChainHandlerViaRpc(
bitcoindChainHandler.bitcoindRpc, bitcoindChainHandler.bitcoindRpc,
bitcoindChainHandler.chainHandler) bitcoindChainHandler.chainHandler)
@ -300,7 +300,7 @@ trait ChainUnitTest
def withBitcoindChainHandlerViaRpc(test: OneArgAsyncTest)(implicit def withBitcoindChainHandlerViaRpc(test: OneArgAsyncTest)(implicit
system: ActorSystem): FutureOutcome = { system: ActorSystem): FutureOutcome = {
val builder: () => Future[BitcoindChainHandlerViaRpc] = { () => val builder: () => Future[BitcoindBaseVersionChainHandlerViaRpc] = { () =>
BitcoinSFixture BitcoinSFixture
.createBitcoind() .createBitcoind()
.flatMap(ChainUnitTest.createChainApiWithBitcoindRpc) .flatMap(ChainUnitTest.createChainApiWithBitcoindRpc)
@ -310,15 +310,6 @@ trait ChainUnitTest
test) test)
} }
def withBitcoindV19ChainHandlerViaRpc(test: OneArgAsyncTest)(implicit
system: ActorSystem): FutureOutcome = {
val builder: () => Future[BitcoindV19ChainHandler] = { () =>
ChainUnitTest.createBitcoindV19ChainHandler()
}
makeDependentFixture(builder, ChainUnitTest.destroyBitcoindV19ChainApi)(
test)
}
final def processHeaders( final def processHeaders(
processorF: Future[ChainApi], processorF: Future[ChainApi],
headers: Vector[BlockHeader], headers: Vector[BlockHeader],
@ -569,19 +560,20 @@ object ChainUnitTest extends ChainVerificationLogger {
def createChainApiWithBitcoindRpc(bitcoind: BitcoindRpcClient)(implicit def createChainApiWithBitcoindRpc(bitcoind: BitcoindRpcClient)(implicit
ec: ExecutionContext, ec: ExecutionContext,
chainAppConfig: ChainAppConfig): Future[BitcoindChainHandlerViaRpc] = { chainAppConfig: ChainAppConfig): Future[
BitcoindBaseVersionChainHandlerViaRpc] = {
val handlerWithGenesisHeaderF = val handlerWithGenesisHeaderF =
ChainUnitTest.setupHeaderTableWithGenesisHeader() ChainUnitTest.setupHeaderTableWithGenesisHeader()
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1) val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
chainHandlerF.map { handler => chainHandlerF.map { handler =>
chain.fixture.BitcoindChainHandlerViaRpc(bitcoind, handler) chain.fixture.BitcoindBaseVersionChainHandlerViaRpc(bitcoind, handler)
} }
} }
def destroyBitcoindChainApiViaRpc( def destroyBitcoindChainApiViaRpc(
bitcoindChainHandler: BitcoindChainHandlerViaRpc)(implicit bitcoindChainHandler: BitcoindBaseVersionChainHandlerViaRpc)(implicit
system: ActorSystem, system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[Unit] = { chainAppConfig: ChainAppConfig): Future[Unit] = {
import system.dispatcher import system.dispatcher
@ -591,30 +583,6 @@ object ChainUnitTest extends ChainVerificationLogger {
stopBitcoindF.flatMap(_ => dropTableF) stopBitcoindF.flatMap(_ => dropTableF)
} }
def createBitcoindV19ChainHandler()(implicit
system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[BitcoindV19ChainHandler] = {
import system.dispatcher
val bitcoindV = BitcoindVersion.V19
BitcoinSFixture
.createBitcoind(Some(bitcoindV))
.flatMap(createChainApiWithBitcoindRpc)
.map { b: BitcoindChainHandlerViaRpc =>
BitcoindV19ChainHandler(
b.bitcoindRpc.asInstanceOf[BitcoindV19RpcClient],
b.chainHandler)
}
}
def destroyBitcoindV19ChainApi(
bitcoindV19ChainHandler: BitcoindV19ChainHandler)(implicit
system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[Unit] = {
val b = BitcoindChainHandlerViaRpc(bitcoindV19ChainHandler.bitcoind,
bitcoindV19ChainHandler.chainHandler)
destroyBitcoindChainApiViaRpc(b)
}
def destroyBitcoind(bitcoind: BitcoindRpcClient)(implicit def destroyBitcoind(bitcoind: BitcoindRpcClient)(implicit
system: ActorSystem): Future[Unit] = { system: ActorSystem): Future[Unit] = {
BitcoindRpcTestUtil.stopServer(bitcoind) BitcoindRpcTestUtil.stopServer(bitcoind)
@ -685,4 +653,66 @@ object ChainUnitTest extends ChainVerificationLogger {
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc) ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
} }
def createBitcoindV19ChainHandler()(implicit
system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[BitcoindV19ChainHandler] = {
import system.dispatcher
val bitcoindV = BitcoindVersion.V19
val bitcoindF = BitcoinSFixture
.createBitcoind(Some(bitcoindV))
.map(_.asInstanceOf[BitcoindV19RpcClient])
bitcoindF.flatMap(b => createBitcoindV19ChainHandler(b))
}
def createBitcoindV19ChainHandler(
bitcoindV19RpcClient: BitcoindV19RpcClient)(implicit
ec: ExecutionContext,
chainAppConfig: ChainAppConfig): Future[BitcoindV19ChainHandler] = {
val chainApiWithBitcoindF = createChainApiWithBitcoindV19Rpc(
bitcoindV19RpcClient)
//now sync the chain api to the bitcoind node
val syncedBitcoindWithChainHandlerF = for {
chainApiWithBitcoind <- chainApiWithBitcoindF
bitcoindWithChainHandler <- SyncUtil.syncBitcoindV19WithChainHandler(
chainApiWithBitcoind)
} yield bitcoindWithChainHandler
syncedBitcoindWithChainHandlerF
}
private def createChainApiWithBitcoindV19Rpc(
bitcoind: BitcoindV19RpcClient)(implicit
ec: ExecutionContext,
chainAppConfig: ChainAppConfig): Future[BitcoindV19ChainHandler] = {
val handlerWithGenesisHeaderF =
ChainUnitTest.setupHeaderTableWithGenesisHeader()
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
chainHandlerF.map { handler =>
BitcoindV19ChainHandler(bitcoind, handler)
}
}
def destroyBitcoindV19ChainApi(
bitcoindV19ChainHandler: BitcoindV19ChainHandler)(implicit
system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[Unit] = {
val b = BitcoindBaseVersionChainHandlerViaRpc(
bitcoindV19ChainHandler.bitcoindRpc,
bitcoindV19ChainHandler.chainHandler)
destroyBitcoindChainApiViaRpc(b)
}
/** Destroys the chain api, but leaves the bitcoind instance running
* so we can cache it
*/
def destroyChainApi()(implicit
system: ActorSystem,
chainAppConfig: ChainAppConfig): Future[Unit] = {
ChainUnitTest.destroyAllTables()(chainAppConfig, system.dispatcher)
}
} }

View File

@ -1,6 +1,13 @@
package org.bitcoins.testkit.chain package org.bitcoins.testkit.chain
import org.bitcoins.chain.blockchain.sync.FilterWithHeaderHash import grizzled.slf4j.Logging
import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.chain.blockchain.sync.{
ChainSync,
FilterSync,
FilterWithHeaderHash
}
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.commons.jsonmodels.bitcoind.GetBlockFilterResult import org.bitcoins.commons.jsonmodels.bitcoind.GetBlockFilterResult
import org.bitcoins.core.api.node import org.bitcoins.core.api.node
import org.bitcoins.core.api.node.{NodeApi, NodeChainQueryApi} import org.bitcoins.core.api.node.{NodeApi, NodeChainQueryApi}
@ -11,9 +18,13 @@ import org.bitcoins.core.util.FutureUtil
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE} import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.rpc.client.common.BitcoindRpcClient import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.testkit.chain.fixture.{
BitcoindBaseVersionChainHandlerViaRpc,
BitcoindChainHandlerViaRpc,
BitcoindV19ChainHandler
}
import org.bitcoins.wallet.Wallet import org.bitcoins.wallet.Wallet
import org.bitcoins.wallet.sync.WalletSync import org.bitcoins.wallet.sync.WalletSync
import grizzled.slf4j.Logging
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
@ -176,6 +187,57 @@ abstract class SyncUtil extends Logging {
getBlockFunc = SyncUtil.getBlockFunc(bitcoind) getBlockFunc = SyncUtil.getBlockFunc(bitcoind)
) )
} }
/** Syncs the given chain handler to the given bitcoind node.
* This does NOT sync this like block filters, as we cannot
* determine if the bitcoind version passed to us has support for block filters
*/
def syncBitcoindWithChainHandler(
bitcoindWithChainHandler: BitcoindChainHandlerViaRpc)(implicit
ec: ExecutionContext): Future[BitcoindBaseVersionChainHandlerViaRpc] = {
val getBestBlockHash = getBestBlockHashFunc(
bitcoindWithChainHandler.bitcoindRpc)
val getBlockHeader = getBlockHeaderFunc(
bitcoindWithChainHandler.bitcoindRpc)
val chainApiF = ChainSync.sync(bitcoindWithChainHandler.chainHandler,
getBlockHeader,
getBestBlockHash)
for {
chainApi <- chainApiF
} yield BitcoindBaseVersionChainHandlerViaRpc(
bitcoindRpc = bitcoindWithChainHandler.bitcoindRpc,
chainHandler = chainApi.asInstanceOf[ChainHandler])
}
/** Syncs the given chain handler to the given bitcoind node. This also syncs block filters
* since we know a bitcoind v19 node has block filter capability
*/
def syncBitcoindV19WithChainHandler(
bitcoindWithChainHandler: BitcoindV19ChainHandler)(implicit
ec: ExecutionContext,
chainAppConfig: ChainAppConfig): Future[BitcoindV19ChainHandler] = {
val bitcoindV19 = bitcoindWithChainHandler.bitcoindRpc
val chainApiF = syncBitcoindWithChainHandler(bitcoindWithChainHandler)
.map(_.chainHandler)
val getFilter: BlockHeader => Future[FilterWithHeaderHash] = {
getFilterFunc(bitcoindV19, FilterType.Basic)
}
for {
chainApi <- chainApiF
filterSyncChainApi <- FilterSync.syncFilters(chainApi, getFilter)
bestBlockHash <- bitcoindV19.getBestBlockHash
ourBestFilter <- chainApi.getBestFilterHeader()
_ = require(
bestBlockHash == ourBestFilter.get.blockHashBE,
s"We did not sync filter's in our fixture bitcoindBestBlockHash=$bestBlockHash our best filter's blockHash=${ourBestFilter.get.blockHashBE}"
)
} yield BitcoindV19ChainHandler(
bitcoindRpc = bitcoindWithChainHandler.bitcoindRpc,
chainHandler = filterSyncChainApi.asInstanceOf[ChainHandler])
}
} }
object SyncUtil extends SyncUtil object SyncUtil extends SyncUtil

View File

@ -2,8 +2,23 @@ package org.bitcoins.testkit.chain.fixture
import org.bitcoins.chain.blockchain.ChainHandler import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.rpc.client.common.BitcoindRpcClient import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
/** Represents a bitcoind instance paired with a chain handler via rpc */ sealed trait BitcoindChainHandlerViaRpc {
case class BitcoindChainHandlerViaRpc( def bitcoindRpc: BitcoindRpcClient
def chainHandler: ChainHandler
}
/** Represents a bitcoind instance paired with a chain handler via rpc
* This is useful for when the bitcoind version doesn't matter, you
* just need a generic [[BitcoindRpcClient]]
*/
case class BitcoindBaseVersionChainHandlerViaRpc(
bitcoindRpc: BitcoindRpcClient, bitcoindRpc: BitcoindRpcClient,
chainHandler: ChainHandler) chainHandler: ChainHandler)
extends BitcoindChainHandlerViaRpc
case class BitcoindV19ChainHandler(
override val bitcoindRpc: BitcoindV19RpcClient,
chainHandler: ChainHandler)
extends BitcoindChainHandlerViaRpc

View File

@ -0,0 +1,89 @@
package org.bitcoins.testkit.chain.fixture
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.testkit.chain.{ChainDbUnitTest, ChainUnitTest}
import org.bitcoins.testkit.rpc.{
CachedBitcoind,
CachedBitcoindNewest,
CachedBitcoindV19
}
import org.scalatest.{FutureOutcome, Outcome}
import scala.concurrent.Future
/** Chain unit test that requires a cached bitcoind type to be injected */
trait ChainWithBitcoindUnitTest extends ChainDbUnitTest {
_: CachedBitcoind[_] =>
}
trait ChainWithBitcoindNewestCachedUnitTest
extends ChainWithBitcoindUnitTest
with CachedBitcoindNewest {
override type FixtureParam = BitcoindBaseVersionChainHandlerViaRpc
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
val f: Future[Outcome] = for {
bitcoind <- cachedBitcoindWithFundsF
futOutcome = withBitcoindNewestChainHandlerViaRpc(test, bitcoind)
fut <- futOutcome.toFuture
} yield fut
new FutureOutcome(f)
}
def withBitcoindNewestChainHandlerViaRpc(
test: OneArgAsyncTest,
bitcoindRpcClient: BitcoindRpcClient): FutureOutcome = {
val builder: () => Future[BitcoindBaseVersionChainHandlerViaRpc] = { () =>
ChainUnitTest.createChainApiWithBitcoindRpc(bitcoindRpcClient)
}
val destroy: BitcoindBaseVersionChainHandlerViaRpc => Future[Unit] = {
case _: BitcoindBaseVersionChainHandlerViaRpc =>
ChainUnitTest.destroyChainApi()
}
makeDependentFixture(builder, destroy)(test)
}
override def afterAll(): Unit = {
super[CachedBitcoindNewest].afterAll()
super[ChainWithBitcoindUnitTest].afterAll()
}
}
/** Chain Unit test suite that has a cached bitcoind v19 instance */
trait ChainWithBitcoindV19CachedUnitTest
extends ChainWithBitcoindUnitTest
with CachedBitcoindV19 {
override type FixtureParam = BitcoindV19ChainHandler
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
val f: Future[Outcome] = for {
bitcoind <- cachedBitcoindWithFundsF
futOutcome = withBitcoindV19ChainHandlerViaRpc(test, bitcoind)
fut <- futOutcome.toFuture
} yield fut
new FutureOutcome(f)
}
def withBitcoindV19ChainHandlerViaRpc(
test: OneArgAsyncTest,
bitcoindV19RpcClient: BitcoindV19RpcClient): FutureOutcome = {
val builder: () => Future[BitcoindV19ChainHandler] = { () =>
ChainUnitTest.createBitcoindV19ChainHandler(bitcoindV19RpcClient)
}
val destroy: BitcoindV19ChainHandler => Future[Unit] = {
case _: BitcoindV19ChainHandler =>
ChainUnitTest.destroyChainApi()
}
makeDependentFixture(builder, destroy)(test)
}
override def afterAll(): Unit = {
super[CachedBitcoindV19].afterAll()
super[ChainWithBitcoindUnitTest].afterAll()
}
}

View File

@ -5,7 +5,6 @@ import org.bitcoins.core.currency._
import org.bitcoins.core.gcs.FilterType import org.bitcoins.core.gcs.FilterType
import org.bitcoins.core.util.FutureUtil import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.wallet.utxo.TxoState import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.testkit.rpc.CachedBitcoindV19
import org.bitcoins.testkit.wallet.{ import org.bitcoins.testkit.wallet.{
BitcoinSWalletTestCachedBitcoinV19, BitcoinSWalletTestCachedBitcoinV19,
WalletWithBitcoindV19 WalletWithBitcoindV19
@ -14,9 +13,7 @@ import org.scalatest.{FutureOutcome, Outcome}
import scala.concurrent.Future import scala.concurrent.Future
class ProcessBlockTest class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoinV19 {
extends BitcoinSWalletTestCachedBitcoinV19
with CachedBitcoindV19 {
override type FixtureParam = WalletWithBitcoindV19 override type FixtureParam = WalletWithBitcoindV19