From f7efc25a4257c63a59fecfd6e44b7cd85d9c6187 Mon Sep 17 00:00:00 2001 From: Ben Carman Date: Sat, 20 Jun 2020 08:47:51 -0500 Subject: [PATCH] Add tests that NodeCallbacks are executed (#1582) * Add tests that NodeCallbacks are executed * Respond to review * Rebase fixes * Formatting changes * Formatting --- .../core/protocol/CompactSizeUInt.scala | 2 + .../node/BroadcastTransactionTest.scala | 3 - .../bitcoins/node/UpdateBloomFilterTest.scala | 7 +- .../peer/DataMessageHandlerTest.scala | 153 ++++++++++++++++++ .../bitcoins/testkit/node/NodeUnitTest.scala | 138 +++++++++++++++- .../fixture/NodeConnectedWithBitcoind.scala | 6 + 6 files changed, 298 insertions(+), 11 deletions(-) create mode 100644 node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala diff --git a/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala b/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala index 108e274eb6..04781eab55 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala @@ -46,6 +46,8 @@ object CompactSizeUInt extends Factory[CompactSizeUInt] { val zero: CompactSizeUInt = CompactSizeUInt(UInt64.zero) + lazy val one: CompactSizeUInt = CompactSizeUInt(UInt64.one) + override def fromBytes(bytes: ByteVector): CompactSizeUInt = { parseCompactSizeUInt(bytes) } diff --git a/node-test/src/test/scala/org/bitcoins/node/BroadcastTransactionTest.scala b/node-test/src/test/scala/org/bitcoins/node/BroadcastTransactionTest.scala index 39fb309a61..be1af0df3f 100644 --- a/node-test/src/test/scala/org/bitcoins/node/BroadcastTransactionTest.scala +++ b/node-test/src/test/scala/org/bitcoins/node/BroadcastTransactionTest.scala @@ -32,9 +32,6 @@ class BroadcastTransactionTest extends NodeUnitTest { private val sendAmount = 1.bitcoin - private val junkAddress: BitcoinAddress = - BitcoinAddress("2NFyxovf6MyxfHqtVjstGzs6HeLqv92Nq4U") - it must "broadcast a transaction" in { param => val SpvNodeFundedWalletBitcoind(node, wallet, rpc, _) = param diff --git a/node-test/src/test/scala/org/bitcoins/node/UpdateBloomFilterTest.scala b/node-test/src/test/scala/org/bitcoins/node/UpdateBloomFilterTest.scala index 894ed6459d..d137b85125 100644 --- a/node-test/src/test/scala/org/bitcoins/node/UpdateBloomFilterTest.scala +++ b/node-test/src/test/scala/org/bitcoins/node/UpdateBloomFilterTest.scala @@ -1,11 +1,9 @@ package org.bitcoins.node import org.bitcoins.core.currency._ -import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.BitcoinSTestAppConfig -import org.bitcoins.testkit.node.NodeUnitTest -import org.bitcoins.testkit.node.SpvNodeFundedWalletBitcoind +import org.bitcoins.testkit.node.{NodeUnitTest, SpvNodeFundedWalletBitcoind} import org.scalatest.{BeforeAndAfter, FutureOutcome} class UpdateBloomFilterTest extends NodeUnitTest with BeforeAndAfter { @@ -20,9 +18,6 @@ class UpdateBloomFilterTest extends NodeUnitTest with BeforeAndAfter { withSpvNodeFundedWalletBitcoind(test, NodeCallbacks.empty, None) } - private val junkAddress: BitcoinAddress = - BitcoinAddress("2NFyxovf6MyxfHqtVjstGzs6HeLqv92Nq4U") - it must "update the bloom filter with a TX" in { param => val SpvNodeFundedWalletBitcoind(spv, wallet, rpc, _) = param diff --git a/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala b/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala new file mode 100644 index 0000000000..7cb8bffa8c --- /dev/null +++ b/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala @@ -0,0 +1,153 @@ +package org.bitcoins.node.networking.peer + +import org.bitcoins.core.currency._ +import org.bitcoins.core.gcs.{FilterType, GolombFilter} +import org.bitcoins.core.p2p._ +import org.bitcoins.core.protocol.CompactSizeUInt +import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, MerkleBlock} +import org.bitcoins.core.protocol.transaction.Transaction +import org.bitcoins.crypto.DoubleSha256Digest +import org.bitcoins.node._ +import org.bitcoins.server.BitcoinSAppConfig +import org.bitcoins.testkit.BitcoinSTestAppConfig +import org.bitcoins.testkit.node.NodeUnitTest +import org.bitcoins.testkit.node.fixture.SpvNodeConnectedWithBitcoindV19 +import org.scalatest.FutureOutcome + +import scala.concurrent.{Future, Promise} + +class DataMessageHandlerTest extends NodeUnitTest { + + /** Wallet config with data directory set to user temp directory */ + implicit override protected def config: BitcoinSAppConfig = + BitcoinSTestAppConfig.getSpvWithEmbeddedDbTestConfig(pgUrl) + + override type FixtureParam = SpvNodeConnectedWithBitcoindV19 + + override def withFixture(test: OneArgAsyncTest): FutureOutcome = + withSpvNodeConnectedToBitcoindV19(test) + + it must "verify OnMerkleBlock callbacks are executed" in { + param: FixtureParam => + val SpvNodeConnectedWithBitcoindV19(spv, bitcoind) = param + + val resultP: Promise[(MerkleBlock, Vector[Transaction])] = Promise() + + for { + sender <- spv.peerMsgSenderF + + txId <- bitcoind.sendToAddress(junkAddress, 1.bitcoin) + tx <- bitcoind.getRawTransactionRaw(txId) + _ <- bitcoind.generateToAddress(blocks = 1, junkAddress) + merkleBlock <- bitcoind.getTxOutProof(Vector(txId)) + + payload1 = MerkleBlockMessage(merkleBlock) + payload2 = TransactionMessage(tx) + + callback: OnMerkleBlockReceived = + (merkle: MerkleBlock, txs: Vector[Transaction]) => { + Future { + resultP.success((merkle, txs)) + () + } + } + + callbacks = NodeCallbacks.onMerkleBlockReceived(callback) + + dataMessageHandler = DataMessageHandler(genesisChainApi, callbacks) + _ <- dataMessageHandler.handleDataPayload(payload1, sender) + _ <- dataMessageHandler.handleDataPayload(payload2, sender) + result <- resultP.future + } yield assert(result == (merkleBlock, Vector(tx))) + } + + it must "verify OnBlockReceived callbacks are executed" in { + param: FixtureParam => + val SpvNodeConnectedWithBitcoindV19(spv, bitcoind) = param + + val resultP: Promise[Block] = Promise() + + for { + sender <- spv.peerMsgSenderF + + hash <- bitcoind.generateToAddress(blocks = 1, junkAddress).map(_.head) + block <- bitcoind.getBlockRaw(hash) + + payload = BlockMessage(block) + + callback: OnBlockReceived = (block: Block) => { + Future { + resultP.success(block) + () + } + } + + callbacks = NodeCallbacks.onBlockReceived(callback) + + dataMessageHandler = DataMessageHandler(genesisChainApi, callbacks) + _ <- dataMessageHandler.handleDataPayload(payload, sender) + result <- resultP.future + } yield assert(result == block) + } + + it must "verify OnBlockHeadersReceived callbacks are executed" in { + param: FixtureParam => + val SpvNodeConnectedWithBitcoindV19(spv, bitcoind) = param + + val resultP: Promise[Vector[BlockHeader]] = Promise() + + for { + sender <- spv.peerMsgSenderF + + hash <- bitcoind.generateToAddress(blocks = 1, junkAddress).map(_.head) + header <- bitcoind.getBlockHeaderRaw(hash) + + payload = HeadersMessage(CompactSizeUInt.one, Vector(header)) + + callback: OnBlockHeadersReceived = (headers: Vector[BlockHeader]) => { + Future { + resultP.success(headers) + () + } + } + + callbacks = NodeCallbacks.onBlockHeadersReceived(callback) + + dataMessageHandler = DataMessageHandler(genesisChainApi, callbacks) + _ <- dataMessageHandler.handleDataPayload(payload, sender) + result <- resultP.future + } yield assert(result == Vector(header)) + } + + it must "verify OnCompactFilterReceived callbacks are executed" in { + param: FixtureParam => + val SpvNodeConnectedWithBitcoindV19(spv, bitcoind) = param + + val resultP: Promise[Vector[(DoubleSha256Digest, GolombFilter)]] = + Promise() + + for { + sender <- spv.peerMsgSenderF + + hash <- bitcoind.generateToAddress(blocks = 1, junkAddress).map(_.head) + filter <- bitcoind.getBlockFilter(hash, FilterType.Basic) + + payload = + CompactFilterMessage(FilterType.Basic, hash.flip, filter.filter.bytes) + + callback: OnCompactFiltersReceived = + (filters: Vector[(DoubleSha256Digest, GolombFilter)]) => { + Future { + resultP.success(filters) + () + } + } + + callbacks = NodeCallbacks.onCompactFilterReceived(callback) + + dataMessageHandler = DataMessageHandler(genesisChainApi, callbacks) + _ <- dataMessageHandler.handleDataPayload(payload, sender) + result <- resultP.future + } yield assert(result == Vector((hash.flip, filter.filter))) + } +} diff --git a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala index 439104a7cf..4aab6e3b1d 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala @@ -5,7 +5,18 @@ import java.net.InetSocketAddress import akka.actor.ActorSystem import org.bitcoins.chain.api.ChainApi import org.bitcoins.chain.config.ChainAppConfig +import org.bitcoins.chain.models.{ + BlockHeaderDb, + CompactFilterDb, + CompactFilterHeaderDb +} +import org.bitcoins.core.api.ChainQueryApi import org.bitcoins.core.config.NetworkParameters +import org.bitcoins.core.gcs.FilterHeader +import org.bitcoins.core.p2p.CompactFilterMessage +import org.bitcoins.core.protocol.blockchain.BlockHeader +import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp} +import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE} import org.bitcoins.db.AppConfig import org.bitcoins.node._ import org.bitcoins.node.config.NodeAppConfig @@ -16,8 +27,9 @@ import org.bitcoins.node.networking.peer.{ PeerMessageReceiverState, PeerMessageSender } -import org.bitcoins.rpc.client.common.BitcoindVersion.V18 +import org.bitcoins.rpc.client.common.BitcoindVersion.{V18, V19} import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} +import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.server.BitcoinSAppConfig._ import org.bitcoins.testkit.EmbeddedPg @@ -27,7 +39,8 @@ import org.bitcoins.testkit.keymanager.KeyManagerTestUtil import org.bitcoins.testkit.node.fixture.{ NeutrinoNodeConnectedWithBitcoind, NodeConnectedWithBitcoind, - SpvNodeConnectedWithBitcoind + SpvNodeConnectedWithBitcoind, + SpvNodeConnectedWithBitcoindV19 } import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletWithBitcoindRpc} @@ -60,6 +73,102 @@ trait NodeUnitTest extends BitcoinSFixture with EmbeddedPg { lazy val bitcoindPeerF = startedBitcoindF.map(NodeTestUtil.getBitcoindPeer) + lazy val junkAddress: BitcoinAddress = + BitcoinAddress("2NFyxovf6MyxfHqtVjstGzs6HeLqv92Nq4U") + + val genesisChainApi: ChainApi = new ChainApi { + + override def processHeaders( + headers: Vector[BlockHeader]): Future[ChainApi] = + Future.successful(this) + + override def getHeader( + hash: DoubleSha256DigestBE): Future[Option[BlockHeaderDb]] = + Future.successful(None) + + override def getHeadersAtHeight( + height: Int): Future[Vector[BlockHeaderDb]] = + Future.successful(Vector.empty) + + override def getBlockCount(): Future[Int] = Future.successful(0) + + override def getBestBlockHeader(): Future[BlockHeaderDb] = + Future.successful(ChainUnitTest.genesisHeaderDb) + + override def processFilterHeaders( + filterHeaders: Vector[FilterHeader], + stopHash: DoubleSha256DigestBE): Future[ChainApi] = + Future.successful(this) + + override def nextHeaderBatchRange( + stopHash: DoubleSha256DigestBE, + batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]] = + Future.successful(None) + + override def nextFilterHeaderBatchRange( + stopHash: DoubleSha256DigestBE, + batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]] = + Future.successful(None) + + override def processFilters( + message: Vector[CompactFilterMessage]): Future[ChainApi] = + Future.successful(this) + + override def processCheckpoints( + checkpoints: Vector[DoubleSha256DigestBE], + blockHash: DoubleSha256DigestBE): Future[ChainApi] = + Future.successful(this) + + override def getFilterHeaderCount(): Future[Int] = Future.successful(0) + + override def getFilterHeadersAtHeight( + height: Int): Future[Vector[CompactFilterHeaderDb]] = + Future.successful(Vector.empty) + + override def getBestFilterHeader(): Future[Option[CompactFilterHeaderDb]] = + Future.successful(None) + + override def getFilterHeader(blockHash: DoubleSha256DigestBE): Future[ + Option[CompactFilterHeaderDb]] = Future.successful(None) + + override def getFilter( + hash: DoubleSha256DigestBE): Future[Option[CompactFilterDb]] = + Future.successful(None) + + override def getFilterCount(): Future[Int] = Future.successful(0) + + override def getFiltersAtHeight( + height: Int): Future[Vector[CompactFilterDb]] = + Future.successful(Vector.empty) + + override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = + Future.successful(0) + + override def getHeadersBetween( + from: BlockHeaderDb, + to: BlockHeaderDb): Future[Vector[BlockHeaderDb]] = + Future.successful(Vector.empty) + + override def getBlockHeight( + blockHash: DoubleSha256DigestBE): Future[Option[Int]] = + Future.successful(None) + + override def getBestBlockHash(): Future[DoubleSha256DigestBE] = + Future.successful(DoubleSha256DigestBE.empty) + + override def getNumberOfConfirmations( + blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]] = + Future.successful(None) + + override def getFiltersBetweenHeights( + startHeight: Int, + endHeight: Int): Future[Vector[ChainQueryApi.FilterResponse]] = + Future.successful(Vector.empty) + + override def epochSecondToBlockHeight(time: Long): Future[Int] = + Future.successful(0) + } + def withSpvNodeConnectedToBitcoind( test: OneArgAsyncTest, versionOpt: Option[BitcoindVersion] = None)(implicit @@ -84,6 +193,31 @@ trait NodeUnitTest extends BitcoinSFixture with EmbeddedPg { )(test) } + def withSpvNodeConnectedToBitcoindV19(test: OneArgAsyncTest)(implicit + system: ActorSystem, + appConfig: BitcoinSAppConfig): FutureOutcome = { + val nodeWithBitcoindBuilder: () => Future[ + SpvNodeConnectedWithBitcoindV19] = { () => + require(appConfig.isSPVEnabled && !appConfig.isNeutrinoEnabled) + for { + bitcoind <- + BitcoinSFixture + .createBitcoindWithFunds(Some(V19)) + .map(_.asInstanceOf[BitcoindV19RpcClient]) + node <- NodeUnitTest.createSpvNode(bitcoind, NodeCallbacks.empty)( + system, + appConfig.chainConf, + appConfig.nodeConf) + } yield SpvNodeConnectedWithBitcoindV19(node, bitcoind) + } + + makeDependentFixture( + build = nodeWithBitcoindBuilder, + destroy = NodeUnitTest.destroyNodeConnectedWithBitcoind( + _: NodeConnectedWithBitcoind)(system, appConfig) + )(test) + } + def withNeutrinoNodeConnectedToBitcoind( test: OneArgAsyncTest, versionOpt: Option[BitcoindVersion] = None)(implicit diff --git a/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala b/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala index 67d612efa4..6a307c209d 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala @@ -2,6 +2,7 @@ package org.bitcoins.testkit.node.fixture import org.bitcoins.node.{NeutrinoNode, Node, SpvNode} import org.bitcoins.rpc.client.common.BitcoindRpcClient +import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient /** Gives us a fixture that has a SPV node connected with the bitcoind instance */ trait NodeConnectedWithBitcoind { @@ -14,6 +15,11 @@ case class SpvNodeConnectedWithBitcoind( bitcoind: BitcoindRpcClient) extends NodeConnectedWithBitcoind +case class SpvNodeConnectedWithBitcoindV19( + node: SpvNode, + bitcoind: BitcoindV19RpcClient) + extends NodeConnectedWithBitcoind + case class NeutrinoNodeConnectedWithBitcoind( node: NeutrinoNode, bitcoind: BitcoindRpcClient)