diff --git a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/ChainSyncTest.scala b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/ChainSyncTest.scala index fd6862c131..44c050ff43 100644 --- a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/ChainSyncTest.scala +++ b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/ChainSyncTest.scala @@ -3,23 +3,20 @@ package org.bitcoins.chain.blockchain.sync import org.bitcoins.chain.blockchain.ChainHandler import org.bitcoins.core.api.chain.ChainApi import org.bitcoins.crypto.DoubleSha256DigestBE -import org.bitcoins.testkit.chain.fixture.BitcoindChainHandlerViaRpc -import org.bitcoins.testkit.chain.{ChainDbUnitTest, SyncUtil} -import org.scalatest.FutureOutcome +import org.bitcoins.testkit.chain.SyncUtil +import org.bitcoins.testkit.chain.fixture.{ + BitcoindBaseVersionChainHandlerViaRpc, + ChainWithBitcoindNewestCachedUnitTest +} import scala.concurrent.Future -class ChainSyncTest extends ChainDbUnitTest { - override type FixtureParam = BitcoindChainHandlerViaRpc - - override def withFixture(test: OneArgAsyncTest): FutureOutcome = { - withBitcoindChainHandlerViaRpc(test) - } +class ChainSyncTest extends ChainWithBitcoindNewestCachedUnitTest { behavior of "ChainSync" it must "sync our chain handler when it is not synced with bitcoind" in { - bitcoindWithChainHandler: BitcoindChainHandlerViaRpc => + bitcoindWithChainHandler: BitcoindBaseVersionChainHandlerViaRpc => val bitcoind = bitcoindWithChainHandler.bitcoindRpc val chainHandler = bitcoindWithChainHandler.chainHandler //first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions @@ -36,14 +33,17 @@ class ChainSyncTest extends ChainDbUnitTest { getBestBlockHashFunc = getBestBlockHashFunc) } - newChainHandlerF.flatMap { chainHandler => - chainHandler.getBlockCount().map(count => assert(count == 1)) - + for { + 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 { - bitcoindWithChainHandler: BitcoindChainHandlerViaRpc => + bitcoindWithChainHandler: BitcoindBaseVersionChainHandlerViaRpc => val bitcoind = bitcoindWithChainHandler.bitcoindRpc val chainHandler = bitcoindWithChainHandler.chainHandler //first we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions @@ -61,9 +61,18 @@ class ChainSyncTest extends ChainDbUnitTest { getBlockHeaderFunc = getBlockHeaderFunc, getBestBlockHashFunc = getBestBlockHashFunc) - newChainHandlerF.flatMap { chainHandler => - chainHandler.getBlockCount().map(count => assert(count == 0)) - } + val newChainHandler2F = for { + 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 { @@ -87,13 +96,15 @@ class ChainSyncTest extends ChainDbUnitTest { } val assertion1F = for { - hashes <- generate1F + _ <- generate1F chainApiSync1 <- sync1F count <- chainApiSync1.getBlockCount() bestHash <- chainApiSync1.getBestBlockHash() + bitcoindBlockCount <- bitcoind.getBlockCount + bitcoindBestBlockHash <- bitcoind.getBestBlockHash } yield { - assert(count == 1) - assert(bestHash == hashes.head) + assert(count == bitcoindBlockCount) + assert(bestHash == bitcoindBestBlockHash) } //let's call sync again and make sure nothing bad happens @@ -106,11 +117,13 @@ class ChainSyncTest extends ChainDbUnitTest { getBlockHeaderFunc = getBlockHeaderFunc, getBestBlockHashFunc = getBestBlockHashFunc) count <- chainApiSync2.getBlockCount() - hashes <- generate1F + _ <- generate1F bestHash <- chainApiSync2.getBestBlockHash() + bitcoindBlockCount <- bitcoind.getBlockCount + bitcoindBestBlockHash <- bitcoind.getBestBlockHash } yield { - assert(count == 1) - assert(bestHash == hashes.head) + assert(count == bitcoindBlockCount) + assert(bestHash == bitcoindBestBlockHash) } sync2F diff --git a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/FilterSyncTest.scala b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/FilterSyncTest.scala index cdef77343a..15db5dca0f 100644 --- a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/FilterSyncTest.scala +++ b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/sync/FilterSyncTest.scala @@ -4,19 +4,15 @@ import org.bitcoins.chain.blockchain.ChainHandler import org.bitcoins.core.api.chain.ChainApi import org.bitcoins.core.gcs.FilterType import org.bitcoins.core.protocol.blockchain.BlockHeader -import org.bitcoins.testkit.chain.fixture.BitcoindV19ChainHandler -import org.bitcoins.testkit.chain.{ChainDbUnitTest, SyncUtil} -import org.scalatest.FutureOutcome +import org.bitcoins.testkit.chain.SyncUtil +import org.bitcoins.testkit.chain.fixture.{ + BitcoindV19ChainHandler, + ChainWithBitcoindV19CachedUnitTest +} import scala.concurrent.Future -class FilterSyncTest extends ChainDbUnitTest { - - override type FixtureParam = BitcoindV19ChainHandler - - override def withFixture(test: OneArgAsyncTest): FutureOutcome = { - withBitcoindV19ChainHandlerViaRpc(test) - } +class FilterSyncTest extends ChainWithBitcoindV19CachedUnitTest { behavior of "FilterSync" @@ -25,19 +21,25 @@ class FilterSyncTest extends ChainDbUnitTest { val initFilterCountF = chainHandler.getFilterCount() val initFilterHeaderCountF = chainHandler.getFilterHeaderCount() + + val bitcoindFilterCountF = bitcoind.getFilterCount() + val initAssertionsF = for { initFilterCount <- initFilterCountF initFilterHeaderCount <- initFilterHeaderCountF + bitcoindFilterCount <- bitcoindFilterCountF } yield { - assert(initFilterCount == 0) - assert(initFilterHeaderCount == 0) + assert(initFilterCount == bitcoindFilterCount) + assert(initFilterHeaderCount == bitcoindFilterCount) } val generated1BlockF = for { _ <- initAssertionsF addr <- bitcoind.getNewAddress hashes <- bitcoind.generateToAddress(1, addr) - } yield hashes + } yield { + hashes + } val syncedF = generated1BlockF.flatMap { _ => syncHelper(fixture) @@ -46,9 +48,10 @@ class FilterSyncTest extends ChainDbUnitTest { for { syncedChainApi <- syncedF filterHeaderCount <- syncedChainApi.getFilterHeaderCount() - _ = assert(filterHeaderCount == 1) + bitcoindFilterCount <- bitcoind.getFilterCount() + _ = assert(filterHeaderCount == bitcoindFilterCount) 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 { @@ -67,10 +70,11 @@ class FilterSyncTest extends ChainDbUnitTest { for { syncedChainApi <- syncedF + bitcoindFilterCount <- bitcoind.getFilterCount() filterHeaderCount <- syncedChainApi.getFilterHeaderCount() - _ = assert(filterHeaderCount == numBlocks) + _ = assert(filterHeaderCount == bitcoindFilterCount) 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 { @@ -93,10 +97,11 @@ class FilterSyncTest extends ChainDbUnitTest { for { syncedChainApi <- sync2F + bitcoindFilterCount <- bitcoind.getFilterCount() filterHeaderCount <- syncedChainApi.getFilterHeaderCount() - _ = assert(filterHeaderCount == 1) + _ = assert(filterHeaderCount == bitcoindFilterCount) filterCount <- syncedChainApi.getFilterCount() - } yield assert(filterCount == 1) + } yield assert(filterCount == bitcoindFilterCount) } private def syncHelper( @@ -111,10 +116,10 @@ class FilterSyncTest extends ChainDbUnitTest { SyncUtil.getFilterFunc(bitcoind, filterType) //first sync the chain - val syncedHeadersF = ChainSync.sync(chainHandler = chainHandler, - getBlockHeaderFunc = getBlockHeaderFunc, - getBestBlockHashFunc = - getBestBlockHashFunc) + val syncedHeadersF: Future[ChainApi] = ChainSync.sync( + chainHandler = chainHandler, + getBlockHeaderFunc = getBlockHeaderFunc, + getBestBlockHashFunc = getBestBlockHashFunc) //now sync filters syncedHeadersF.flatMap { syncedChainHandler => diff --git a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/ChainSync.scala b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/ChainSync.scala index 366a657f50..4d72307e33 100644 --- a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/ChainSync.scala +++ b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/ChainSync.scala @@ -68,7 +68,6 @@ abstract class ChainSync extends ChainVerificationLogger { getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader])(implicit ec: ExecutionContext): Future[ChainApi] = { 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 val tipsBH = tips.map(_.blockHeader) diff --git a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterSync.scala b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterSync.scala index ea829776b0..49f0e67196 100644 --- a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterSync.scala +++ b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterSync.scala @@ -34,7 +34,6 @@ abstract class FilterSync extends ChainVerificationLogger { batchSize: Int = 25)(implicit ec: ExecutionContext, chainAppConfig: ChainAppConfig): Future[ChainApi] = { - val ourBestFilterHeaderOptF = chainApi.getBestFilterHeader() val ourBestBlockHeaderF = chainApi.getBestBlockHeader() for { @@ -96,7 +95,17 @@ abstract class FilterSync extends ChainVerificationLogger { missing <- chainApi.getHeadersBetween(from = bestFilterBlockHeader.get, to = ourBestHeader) } 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 diff --git a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterWithHeaderHash.scala b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterWithHeaderHash.scala index 30c315257e..b5ba373d08 100644 --- a/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterWithHeaderHash.scala +++ b/chain/src/main/scala/org/bitcoins/chain/blockchain/sync/FilterWithHeaderHash.scala @@ -5,7 +5,8 @@ import org.bitcoins.crypto.DoubleSha256DigestBE /** 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 + * @see https://developer.bitcoin.org/reference/rpc/getblockfilter.html#argument-2-filtertype */ case class FilterWithHeaderHash( filter: GolombFilter, - headerHash: DoubleSha256DigestBE) + filterHeaderHash: DoubleSha256DigestBE) diff --git a/docs/chain/filter-sync.md b/docs/chain/filter-sync.md index f945d030e2..e03a6b3a89 100644 --- a/docs/chain/filter-sync.md +++ b/docs/chain/filter-sync.md @@ -64,7 +64,7 @@ implicit val chainAppConfig = BitcoinSTestAppConfig.getNeutrinoTestConfig().chai val bitcoindWithChainApiF: Future[BitcoindV19ChainHandler] = { ChainUnitTest.createBitcoindV19ChainHandler() } -val bitcoindF = bitcoindWithChainApiF.map(_.bitcoind) +val bitcoindF = bitcoindWithChainApiF.map(_.bitcoindRpc) val chainApiF = bitcoindWithChainApiF.map(_.chainHandler) val filterType = FilterType.Basic diff --git a/testkit/src/main/resources/reference.conf b/testkit/src/main/resources/reference.conf index 5a48edb5e6..8f9e731765 100644 --- a/testkit/src/main/resources/reference.conf +++ b/testkit/src/main/resources/reference.conf @@ -173,6 +173,7 @@ akka { event-stream=off } + # https://doc.akka.io/docs/akka/current/dispatchers.html#classic-dispatchers default-dispatcher { # The goal here is to reduce the number of threads spun up for test suites. # Since every test suite currently @@ -194,20 +195,20 @@ akka { executor = "thread-pool-executor" throughput = 1 thread-pool-executor { - fixed-pool-size = 2 + fixed-pool-size = 1 } } internal-dispatcher { # minimum of one thread for tests parallelism-min = 1 - # maximum of 2 threads for tests - parallelism-max = 2 + # maximum of 1 threads for tests + parallelism-max = 1 type = "Dispatcher" executor = "thread-pool-executor" throughput = 1 thread-pool-executor { - fixed-pool-size = 2 + fixed-pool-size = 1 } } } diff --git a/testkit/src/main/scala/org/bitcoins/testkit/chain/ChainUnitTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/chain/ChainUnitTest.scala index 2efbe5329d..968277f4de 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/chain/ChainUnitTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/chain/ChainUnitTest.scala @@ -1,11 +1,10 @@ package org.bitcoins.testkit.chain -import java.net.InetSocketAddress import akka.actor.ActorSystem import com.typesafe.config.ConfigFactory import org.bitcoins.chain.ChainVerificationLogger -import org.bitcoins.chain.blockchain.{ChainHandler, ChainHandlerCached} import org.bitcoins.chain.blockchain.sync.ChainSync +import org.bitcoins.chain.blockchain.{ChainHandler, ChainHandlerCached} import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.models._ import org.bitcoins.chain.pow.Pow @@ -33,9 +32,10 @@ import org.bitcoins.zmq.ZMQSubscriber import org.scalatest._ import play.api.libs.json.{JsError, JsSuccess, Json} +import java.net.InetSocketAddress import scala.annotation.tailrec -import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration.DurationInt +import scala.concurrent.{ExecutionContext, Future} trait ChainUnitTest extends BitcoinSFixture @@ -271,7 +271,7 @@ trait ChainUnitTest bitcoindChainHandler: BitcoindChainHandlerViaZmq): Future[Unit] = { //piggy back off of rpc destructor - val rpc = chain.fixture.BitcoindChainHandlerViaRpc( + val rpc = chain.fixture.BitcoindBaseVersionChainHandlerViaRpc( bitcoindChainHandler.bitcoindRpc, bitcoindChainHandler.chainHandler) @@ -300,7 +300,7 @@ trait ChainUnitTest def withBitcoindChainHandlerViaRpc(test: OneArgAsyncTest)(implicit system: ActorSystem): FutureOutcome = { - val builder: () => Future[BitcoindChainHandlerViaRpc] = { () => + val builder: () => Future[BitcoindBaseVersionChainHandlerViaRpc] = { () => BitcoinSFixture .createBitcoind() .flatMap(ChainUnitTest.createChainApiWithBitcoindRpc) @@ -310,15 +310,6 @@ trait ChainUnitTest test) } - def withBitcoindV19ChainHandlerViaRpc(test: OneArgAsyncTest)(implicit - system: ActorSystem): FutureOutcome = { - val builder: () => Future[BitcoindV19ChainHandler] = { () => - ChainUnitTest.createBitcoindV19ChainHandler() - } - makeDependentFixture(builder, ChainUnitTest.destroyBitcoindV19ChainApi)( - test) - } - final def processHeaders( processorF: Future[ChainApi], headers: Vector[BlockHeader], @@ -569,19 +560,20 @@ object ChainUnitTest extends ChainVerificationLogger { def createChainApiWithBitcoindRpc(bitcoind: BitcoindRpcClient)(implicit ec: ExecutionContext, - chainAppConfig: ChainAppConfig): Future[BitcoindChainHandlerViaRpc] = { + chainAppConfig: ChainAppConfig): Future[ + BitcoindBaseVersionChainHandlerViaRpc] = { val handlerWithGenesisHeaderF = ChainUnitTest.setupHeaderTableWithGenesisHeader() val chainHandlerF = handlerWithGenesisHeaderF.map(_._1) chainHandlerF.map { handler => - chain.fixture.BitcoindChainHandlerViaRpc(bitcoind, handler) + chain.fixture.BitcoindBaseVersionChainHandlerViaRpc(bitcoind, handler) } } def destroyBitcoindChainApiViaRpc( - bitcoindChainHandler: BitcoindChainHandlerViaRpc)(implicit + bitcoindChainHandler: BitcoindBaseVersionChainHandlerViaRpc)(implicit system: ActorSystem, chainAppConfig: ChainAppConfig): Future[Unit] = { import system.dispatcher @@ -591,30 +583,6 @@ object ChainUnitTest extends ChainVerificationLogger { 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 system: ActorSystem): Future[Unit] = { BitcoindRpcTestUtil.stopServer(bitcoind) @@ -685,4 +653,66 @@ object ChainUnitTest extends ChainVerificationLogger { 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) + } } diff --git a/testkit/src/main/scala/org/bitcoins/testkit/chain/SyncUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/chain/SyncUtil.scala index 0cf2441631..f0b5f7ab21 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/chain/SyncUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/chain/SyncUtil.scala @@ -1,6 +1,13 @@ 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.core.api.node 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.rpc.client.common.BitcoindRpcClient 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.sync.WalletSync -import grizzled.slf4j.Logging import scala.concurrent.{ExecutionContext, Future} @@ -176,6 +187,57 @@ abstract class SyncUtil extends Logging { 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 diff --git a/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/BitcoindChainHandlerViaRpc.scala b/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/BitcoindChainHandlerViaRpc.scala index fc45d7212b..da04401dc9 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/BitcoindChainHandlerViaRpc.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/BitcoindChainHandlerViaRpc.scala @@ -2,8 +2,23 @@ package org.bitcoins.testkit.chain.fixture import org.bitcoins.chain.blockchain.ChainHandler 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 */ -case class BitcoindChainHandlerViaRpc( +sealed trait 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, chainHandler: ChainHandler) + extends BitcoindChainHandlerViaRpc + +case class BitcoindV19ChainHandler( + override val bitcoindRpc: BitcoindV19RpcClient, + chainHandler: ChainHandler) + extends BitcoindChainHandlerViaRpc diff --git a/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/ChainWithBitcoindUnitTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/ChainWithBitcoindUnitTest.scala new file mode 100644 index 0000000000..c7665f8f86 --- /dev/null +++ b/testkit/src/main/scala/org/bitcoins/testkit/chain/fixture/ChainWithBitcoindUnitTest.scala @@ -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() + } +} diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessBlockTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessBlockTest.scala index 1c5e185ce9..dea42b2af9 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessBlockTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessBlockTest.scala @@ -5,7 +5,6 @@ import org.bitcoins.core.currency._ import org.bitcoins.core.gcs.FilterType import org.bitcoins.core.util.FutureUtil import org.bitcoins.core.wallet.utxo.TxoState -import org.bitcoins.testkit.rpc.CachedBitcoindV19 import org.bitcoins.testkit.wallet.{ BitcoinSWalletTestCachedBitcoinV19, WalletWithBitcoindV19 @@ -14,9 +13,7 @@ import org.scalatest.{FutureOutcome, Outcome} import scala.concurrent.Future -class ProcessBlockTest - extends BitcoinSWalletTestCachedBitcoinV19 - with CachedBitcoindV19 { +class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoinV19 { override type FixtureParam = WalletWithBitcoindV19