mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
Start the process of refactoring our ChainHandler to be able to avoid… (#655)
* Start the process of refactoring our ChainHandler to be able to avoid database calls on TipValidation WIP: Begin explicity passing state back and forth in return types of PeerMessageReceiver, P2PClient, , DataMessageHandler. This commit also implements the ability to keep our blockchain completely in memory. Previously when we were updating the tip of the chain, we had to make a database read to figure out what the best tips are. This is suboptimal for performance because a database read needs to be done for every block header we see, now we just keep the chain in memory Fix bug in DataMessageHandler that pre-emptively sent a getheadersmsg to our peer. Make 'chainApiF' internal to our spvNode (not a parameter). This forces the chainApi to be created from disk everytime a new SpvNode is spun up. This keeps us in sync with the blockchain at disk at the cost of disk access and less modularity of SpvNode Address torkel code review Fix rebase issues Address code review Address nadav code review * Rebase onto master, fix api changes
This commit is contained in:
parent
c934d8efc2
commit
b0b1c1cc42
20 changed files with 710 additions and 392 deletions
|
@ -1,30 +1,22 @@
|
||||||
package org.bitcoins.server
|
package org.bitcoins.server
|
||||||
|
|
||||||
import org.bitcoins.rpc.config.BitcoindInstance
|
|
||||||
import org.bitcoins.node.models.Peer
|
|
||||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
|
||||||
import akka.actor.ActorSystem
|
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import scala.concurrent.Future
|
|
||||||
import org.bitcoins.wallet.LockedWallet
|
import akka.actor.ActorSystem
|
||||||
import org.bitcoins.wallet.Wallet
|
|
||||||
import org.bitcoins.wallet.api.InitializeWalletSuccess
|
|
||||||
import org.bitcoins.wallet.api.InitializeWalletError
|
|
||||||
import org.bitcoins.node.SpvNode
|
|
||||||
import org.bitcoins.chain.blockchain.ChainHandler
|
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.wallet.api.UnlockedWalletApi
|
|
||||||
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
|
||||||
import org.bitcoins.wallet.api.UnlockWalletError
|
|
||||||
import org.bitcoins.node.networking.peer.DataMessageHandler
|
|
||||||
import org.bitcoins.node.SpvNodeCallbacks
|
|
||||||
import org.bitcoins.wallet.WalletStorage
|
|
||||||
import org.bitcoins.db.AppLoggers
|
import org.bitcoins.db.AppLoggers
|
||||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
import org.bitcoins.node.{SpvNode, SpvNodeCallbacks}
|
||||||
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
|
import org.bitcoins.node.models.Peer
|
||||||
|
import org.bitcoins.node.networking.peer.DataMessageHandler
|
||||||
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
|
import org.bitcoins.rpc.config.BitcoindInstance
|
||||||
|
import org.bitcoins.wallet.{LockedWallet, Wallet, WalletStorage}
|
||||||
|
import org.bitcoins.wallet.api._
|
||||||
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
|
|
||||||
|
import scala.concurrent.{Await, Future}
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
object Main extends App {
|
object Main extends App {
|
||||||
implicit val conf = {
|
implicit val conf = {
|
||||||
|
@ -107,20 +99,17 @@ object Main extends App {
|
||||||
|
|
||||||
SpvNodeCallbacks(onTxReceived = Seq(onTX))
|
SpvNodeCallbacks(onTxReceived = Seq(onTX))
|
||||||
}
|
}
|
||||||
val blockheaderDAO = BlockHeaderDAO()
|
SpvNode(peer, bloom, callbacks).start()
|
||||||
val chain = ChainHandler(blockheaderDAO)
|
|
||||||
SpvNode(peer, chain, bloom, callbacks).start()
|
|
||||||
}
|
}
|
||||||
_ = logger.info(s"Starting SPV node sync")
|
_ = logger.info(s"Starting SPV node sync")
|
||||||
_ <- node.sync()
|
_ <- node.sync()
|
||||||
|
chainApi <- node.chainApiFromDb()
|
||||||
start <- {
|
start <- {
|
||||||
val walletRoutes = WalletRoutes(wallet, node)
|
val walletRoutes = WalletRoutes(wallet, node)
|
||||||
val nodeRoutes = NodeRoutes(node)
|
val nodeRoutes = NodeRoutes(node)
|
||||||
val chainRoutes = ChainRoutes(node.chainApi)
|
val chainRoutes = ChainRoutes(chainApi)
|
||||||
val server =
|
val server = Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes))
|
||||||
Server(nodeConf, // could use either of configurations
|
|
||||||
Seq(walletRoutes, nodeRoutes, chainRoutes))
|
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
} yield {
|
} yield {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class BlockchainTest extends ChainUnitTest {
|
||||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
||||||
withBlockHeaderDAO(test)
|
withBlockHeaderDAO(test)
|
||||||
|
|
||||||
override implicit val system: ActorSystem = ActorSystem("BlockchainTest")
|
implicit override val system: ActorSystem = ActorSystem("BlockchainTest")
|
||||||
|
|
||||||
behavior of "Blockchain"
|
behavior of "Blockchain"
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ class BlockchainTest extends ChainUnitTest {
|
||||||
BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb)
|
BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb)
|
||||||
|
|
||||||
val connectTipF = Blockchain.connectTip(header = newHeader.blockHeader,
|
val connectTipF = Blockchain.connectTip(header = newHeader.blockHeader,
|
||||||
blockHeaderDAO = bhDAO)
|
blockHeaderDAO = bhDAO,
|
||||||
|
Vector(blockchain))
|
||||||
|
|
||||||
connectTipF.map {
|
connectTipF.map {
|
||||||
case BlockchainUpdate.Successful(_, connectedHeader) =>
|
case BlockchainUpdate.Successful(_, connectedHeader) =>
|
||||||
|
|
|
@ -120,7 +120,9 @@ class ChainHandlerTest extends ChainUnitTest {
|
||||||
val createdF = chainHandler.blockHeaderDAO.createAll(firstThreeBlocks)
|
val createdF = chainHandler.blockHeaderDAO.createAll(firstThreeBlocks)
|
||||||
|
|
||||||
createdF.flatMap { _ =>
|
createdF.flatMap { _ =>
|
||||||
val processorF = Future.successful(chainHandler)
|
val blockchain = Blockchain.fromHeaders(firstThreeBlocks.reverse)
|
||||||
|
val handler = ChainHandler(chainHandler.blockHeaderDAO, blockchain)
|
||||||
|
val processorF = Future.successful(handler)
|
||||||
// Takes way too long to do all blocks
|
// Takes way too long to do all blocks
|
||||||
val blockHeadersToTest = blockHeaders.tail
|
val blockHeadersToTest = blockHeaders.tail
|
||||||
.take(
|
.take(
|
||||||
|
|
|
@ -44,6 +44,7 @@ case class Blockchain(headers: Vector[BlockHeaderDb])
|
||||||
object Blockchain extends ChainVerificationLogger {
|
object Blockchain extends ChainVerificationLogger {
|
||||||
|
|
||||||
def fromHeaders(headers: Vector[BlockHeaderDb]): Blockchain = {
|
def fromHeaders(headers: Vector[BlockHeaderDb]): Blockchain = {
|
||||||
|
|
||||||
Blockchain(headers)
|
Blockchain(headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,36 +61,37 @@ object Blockchain extends ChainVerificationLogger {
|
||||||
* we [[org.bitcoins.chain.blockchain.BlockchainUpdate.Successful successful]] connected the tip,
|
* we [[org.bitcoins.chain.blockchain.BlockchainUpdate.Successful successful]] connected the tip,
|
||||||
* or [[org.bitcoins.chain.blockchain.BlockchainUpdate.Failed Failed]] to connect to a tip
|
* or [[org.bitcoins.chain.blockchain.BlockchainUpdate.Failed Failed]] to connect to a tip
|
||||||
*/
|
*/
|
||||||
def connectTip(header: BlockHeader, blockHeaderDAO: BlockHeaderDAO)(
|
def connectTip(
|
||||||
|
header: BlockHeader,
|
||||||
|
blockHeaderDAO: BlockHeaderDAO,
|
||||||
|
blockchains: Vector[Blockchain])(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
conf: ChainAppConfig): Future[BlockchainUpdate] = {
|
conf: ChainAppConfig): Future[BlockchainUpdate] = {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain")
|
s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain")
|
||||||
|
|
||||||
//get all competing chains we have
|
val tipResultF: Future[BlockchainUpdate] = {
|
||||||
val blockchainsF: Future[Vector[Blockchain]] =
|
|
||||||
blockHeaderDAO.getBlockchains()
|
|
||||||
|
|
||||||
val tipResultF: Future[BlockchainUpdate] = blockchainsF.flatMap {
|
|
||||||
blockchains =>
|
|
||||||
val nested: Vector[Future[BlockchainUpdate]] = blockchains.map {
|
val nested: Vector[Future[BlockchainUpdate]] = blockchains.map {
|
||||||
blockchain =>
|
blockchain =>
|
||||||
val prevBlockHeaderOpt =
|
val prevBlockHeaderIdxOpt =
|
||||||
blockchain.find(_.hashBE == header.previousBlockHashBE)
|
blockchain.headers.zipWithIndex.find {
|
||||||
prevBlockHeaderOpt match {
|
case (headerDb, _) =>
|
||||||
|
headerDb.hashBE == header.previousBlockHashBE
|
||||||
|
}
|
||||||
|
prevBlockHeaderIdxOpt match {
|
||||||
case None =>
|
case None =>
|
||||||
logger.warn(
|
logger.warn(
|
||||||
s"No common ancestor found in the chain to connect header=${header.hashBE.hex}")
|
s"No common ancestor found in the chain to connect to ${header.hashBE}")
|
||||||
val err = TipUpdateResult.BadPreviousBlockHash(header)
|
val err = TipUpdateResult.BadPreviousBlockHash(header)
|
||||||
val failed = BlockchainUpdate.Failed(blockchain = blockchain,
|
val failed = BlockchainUpdate.Failed(blockchain = blockchain,
|
||||||
failedHeader = header,
|
failedHeader = header,
|
||||||
tipUpdateFailure = err)
|
tipUpdateFailure = err)
|
||||||
Future.successful(failed)
|
Future.successful(failed)
|
||||||
|
|
||||||
case Some(prevBlockHeader) =>
|
case Some((prevBlockHeader, prevHeaderIdx)) =>
|
||||||
//found a header to connect to!
|
//found a header to connect to!
|
||||||
logger.trace(
|
logger.debug(
|
||||||
s"Found ancestor=${prevBlockHeader.hashBE.hex} for header=${header.hashBE.hex}")
|
s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain")
|
||||||
val tipResultF =
|
val tipResultF =
|
||||||
TipValidation.checkNewTip(newPotentialTip = header,
|
TipValidation.checkNewTip(newPotentialTip = header,
|
||||||
currentTip = prevBlockHeader,
|
currentTip = prevBlockHeader,
|
||||||
|
@ -100,8 +102,10 @@ object Blockchain extends ChainVerificationLogger {
|
||||||
case TipUpdateResult.Success(headerDb) =>
|
case TipUpdateResult.Success(headerDb) =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Successfully verified=${headerDb.hashBE.hex}, connecting to chain")
|
s"Successfully verified=${headerDb.hashBE.hex}, connecting to chain")
|
||||||
|
val oldChain =
|
||||||
|
blockchain.takeRight(blockchain.length - prevHeaderIdx)
|
||||||
val newChain =
|
val newChain =
|
||||||
Blockchain.fromHeaders(headerDb +: blockchain.headers)
|
Blockchain.fromHeaders(headerDb +: oldChain)
|
||||||
BlockchainUpdate.Successful(newChain, headerDb)
|
BlockchainUpdate.Successful(newChain, headerDb)
|
||||||
case fail: TipUpdateResult.Failure =>
|
case fail: TipUpdateResult.Failure =>
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
@ -113,6 +117,7 @@ object Blockchain extends ChainVerificationLogger {
|
||||||
}
|
}
|
||||||
parseSuccessOrFailure(nested = nested)
|
parseSuccessOrFailure(nested = nested)
|
||||||
}
|
}
|
||||||
|
|
||||||
tipResultF
|
tipResultF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@ import org.bitcoins.chain.validation.TipUpdateResult
|
||||||
* of [[org.bitcoins.chain.api.ChainApi ChainApi]], this is the entry point in to the
|
* of [[org.bitcoins.chain.api.ChainApi ChainApi]], this is the entry point in to the
|
||||||
* chain project.
|
* chain project.
|
||||||
*/
|
*/
|
||||||
case class ChainHandler(blockHeaderDAO: BlockHeaderDAO)(
|
case class ChainHandler(
|
||||||
implicit private[chain] val chainConfig: ChainAppConfig
|
blockHeaderDAO: BlockHeaderDAO,
|
||||||
) extends ChainApi
|
blockchains: Vector[Blockchain])(
|
||||||
|
implicit private[chain] val chainConfig: ChainAppConfig)
|
||||||
|
extends ChainApi
|
||||||
with ChainVerificationLogger {
|
with ChainVerificationLogger {
|
||||||
|
|
||||||
override def getBlockCount(implicit ec: ExecutionContext): Future[Long] = {
|
override def getBlockCount(implicit ec: ExecutionContext): Future[Long] = {
|
||||||
|
@ -49,18 +51,40 @@ case class ChainHandler(blockHeaderDAO: BlockHeaderDAO)(
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Processing header=${header.hashBE.hex}, previousHash=${header.previousBlockHashBE.hex}")
|
s"Processing header=${header.hashBE.hex}, previousHash=${header.previousBlockHashBE.hex}")
|
||||||
|
|
||||||
val blockchainUpdateF =
|
val blockchainUpdateF = Blockchain.connectTip(header = header,
|
||||||
Blockchain.connectTip(header, blockHeaderDAO)
|
blockHeaderDAO =
|
||||||
|
blockHeaderDAO,
|
||||||
|
blockchains = blockchains)
|
||||||
|
|
||||||
val newHandlerF = blockchainUpdateF.flatMap {
|
val newHandlerF = blockchainUpdateF.flatMap {
|
||||||
case BlockchainUpdate.Successful(_, updatedHeader) =>
|
case BlockchainUpdate.Successful(newChain, updatedHeader) =>
|
||||||
//now we have successfully connected the header, we need to insert
|
//now we have successfully connected the header, we need to insert
|
||||||
//it into the database
|
//it into the database
|
||||||
val createdF = blockHeaderDAO.create(updatedHeader)
|
val createdF = blockHeaderDAO.create(updatedHeader)
|
||||||
createdF.map { header =>
|
createdF.map { header =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Connected new header to blockchain, height=${header.height} hash=${header.hashBE.hex}")
|
s"Connected new header to blockchain, height=${header.height} hash=${header.hashBE}")
|
||||||
ChainHandler(blockHeaderDAO)
|
val chainIdxOpt = blockchains.zipWithIndex.find {
|
||||||
|
case (chain, _) =>
|
||||||
|
val oldTip = newChain(1) //should be safe, even with genesis header as we just connected a tip
|
||||||
|
oldTip == chain.tip
|
||||||
|
}
|
||||||
|
|
||||||
|
val updatedChains = {
|
||||||
|
chainIdxOpt match {
|
||||||
|
case Some((_, idx)) =>
|
||||||
|
logger.trace(
|
||||||
|
s"Updating chain at idx=${idx} out of competing chains=${blockchains.length} with new tip=${header.hashBE.hex}")
|
||||||
|
blockchains.updated(idx, newChain)
|
||||||
|
|
||||||
|
case None =>
|
||||||
|
logger.info(
|
||||||
|
s"New competing blockchain with tip=${newChain.tip}")
|
||||||
|
blockchains.:+(newChain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainHandler(blockHeaderDAO, updatedChains)
|
||||||
}
|
}
|
||||||
case BlockchainUpdate.Failed(_, _, reason) =>
|
case BlockchainUpdate.Failed(_, _, reason) =>
|
||||||
val errMsg =
|
val errMsg =
|
||||||
|
@ -117,11 +141,42 @@ case class ChainHandler(blockHeaderDAO: BlockHeaderDAO)(
|
||||||
//this does _not_ mean that it is on the chain that has the most work
|
//this does _not_ mean that it is on the chain that has the most work
|
||||||
//TODO: Enhance this in the future to return the "heaviest" header
|
//TODO: Enhance this in the future to return the "heaviest" header
|
||||||
//https://bitcoin.org/en/glossary/block-chain
|
//https://bitcoin.org/en/glossary/block-chain
|
||||||
blockHeaderDAO.chainTips.map { tips =>
|
val groupedChains = blockchains.groupBy(_.tip.height)
|
||||||
val sorted = tips.sortBy(header => header.blockHeader.difficulty)
|
val maxHeight = groupedChains.keys.max
|
||||||
val hash = sorted.head.hashBE
|
val chains = groupedChains(maxHeight)
|
||||||
logger.debug(s"getBestBlockHash result: hash=$hash")
|
|
||||||
hash
|
val hashBE: DoubleSha256DigestBE = chains match {
|
||||||
|
case Vector() =>
|
||||||
|
val errMsg = s"Did not find blockchain with height $maxHeight"
|
||||||
|
logger.error(errMsg)
|
||||||
|
throw new RuntimeException(errMsg)
|
||||||
|
case chain +: Vector() =>
|
||||||
|
chain.tip.hashBE
|
||||||
|
case chain +: rest =>
|
||||||
|
logger.warn(
|
||||||
|
s"We have multiple competing blockchains: ${(chain +: rest).map(_.tip.hashBE.hex).mkString(", ")}")
|
||||||
|
chain.tip.hashBE
|
||||||
}
|
}
|
||||||
|
Future.successful(hashBE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ChainHandler {
|
||||||
|
|
||||||
|
/** Constructs a [[ChainHandler chain handler]] from the state in the database
|
||||||
|
* This gives us the guaranteed latest state we have in the database
|
||||||
|
* */
|
||||||
|
def fromDatabase(blockHeaderDAO: BlockHeaderDAO)(
|
||||||
|
implicit ec: ExecutionContext,
|
||||||
|
chainConfig: ChainAppConfig): Future[ChainHandler] = {
|
||||||
|
val bestChainsF = blockHeaderDAO.getBlockchains()
|
||||||
|
|
||||||
|
bestChainsF.map(chains =>
|
||||||
|
new ChainHandler(blockHeaderDAO = blockHeaderDAO, blockchains = chains))
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply(blockHeaderDAO: BlockHeaderDAO, blockchains: Blockchain)(
|
||||||
|
implicit chainConfig: ChainAppConfig): ChainHandler = {
|
||||||
|
new ChainHandler(blockHeaderDAO, Vector(blockchains))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,21 @@ object FutureUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
val unit: Future[Unit] = Future.successful(())
|
val unit: Future[Unit] = Future.successful(())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folds over the given elements sequentially in a non-blocking async way
|
||||||
|
* @param init the initialized value for the accumulator
|
||||||
|
* @param items the items we are folding over
|
||||||
|
* @param fun the function we are applying to every element that returns a future
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def foldLeftAsync[T, U](init: T, items: Seq[U])(fun: (T, U) => Future[T])(
|
||||||
|
implicit ec: ExecutionContext): Future[T] = {
|
||||||
|
items.foldLeft(Future.successful(init)) {
|
||||||
|
case (accumF, elem) =>
|
||||||
|
accumF.flatMap { accum =>
|
||||||
|
fun(accum, elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,16 +58,10 @@ class BroadcastTransactionTest extends BitcoinSWalletTest {
|
||||||
|
|
||||||
address <- rpc.getNewAddress
|
address <- rpc.getNewAddress
|
||||||
bloom <- wallet.getBloomFilter()
|
bloom <- wallet.getBloomFilter()
|
||||||
|
|
||||||
spv <- {
|
spv <- {
|
||||||
val peer = Peer.fromBitcoind(rpc.instance)
|
val peer = Peer.fromBitcoind(rpc.instance)
|
||||||
val chainHandler = {
|
|
||||||
val bhDao = BlockHeaderDAO()
|
|
||||||
ChainHandler(bhDao)
|
|
||||||
}
|
|
||||||
|
|
||||||
val spv =
|
val spv = SpvNode(peer, bloomFilter = bloom)
|
||||||
SpvNode(peer, chainHandler, bloomFilter = bloom)
|
|
||||||
spv.start()
|
spv.start()
|
||||||
}
|
}
|
||||||
_ <- spv.sync()
|
_ <- spv.sync()
|
||||||
|
|
|
@ -90,7 +90,7 @@ class NodeWithWalletTest extends NodeUnitTest {
|
||||||
bloom <- wallet.getBloomFilter()
|
bloom <- wallet.getBloomFilter()
|
||||||
address <- wallet.getNewAddress()
|
address <- wallet.getNewAddress()
|
||||||
spv <- initSpv.start()
|
spv <- initSpv.start()
|
||||||
updatedBloom = spv.updateBloomFilter(address).bloomFilter
|
updatedBloom <- spv.updateBloomFilter(address).map(_.bloomFilter)
|
||||||
_ <- spv.sync()
|
_ <- spv.sync()
|
||||||
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,10 @@ class SpvNodeTest extends NodeUnitTest {
|
||||||
val spvNode = spvNodeConnectedWithBitcoind.spvNode
|
val spvNode = spvNodeConnectedWithBitcoind.spvNode
|
||||||
val bitcoind = spvNodeConnectedWithBitcoind.bitcoind
|
val bitcoind = spvNodeConnectedWithBitcoind.bitcoind
|
||||||
|
|
||||||
assert(spvNode.isConnected)
|
val assert1F = for {
|
||||||
|
_ <- spvNode.isConnected.map(assert(_))
|
||||||
assert(spvNode.isInitialized)
|
a2 <- spvNode.isInitialized.map(assert(_))
|
||||||
|
} yield a2
|
||||||
|
|
||||||
val hashF: Future[DoubleSha256DigestBE] = {
|
val hashF: Future[DoubleSha256DigestBE] = {
|
||||||
bitcoind.generate(1).map(_.head)
|
bitcoind.generate(1).map(_.head)
|
||||||
|
@ -37,6 +38,7 @@ class SpvNodeTest extends NodeUnitTest {
|
||||||
|
|
||||||
//sync our spv node expecting to get that generated hash
|
//sync our spv node expecting to get that generated hash
|
||||||
val spvSyncF = for {
|
val spvSyncF = for {
|
||||||
|
_ <- assert1F
|
||||||
_ <- hashF
|
_ <- hashF
|
||||||
sync <- spvNode.sync()
|
sync <- spvNode.sync()
|
||||||
} yield sync
|
} yield sync
|
||||||
|
@ -62,7 +64,9 @@ class SpvNodeTest extends NodeUnitTest {
|
||||||
//as they happen with the 'sendheaders' message
|
//as they happen with the 'sendheaders' message
|
||||||
//both our spv node and our bitcoind node _should_ both be at the genesis block (regtest)
|
//both our spv node and our bitcoind node _should_ both be at the genesis block (regtest)
|
||||||
//at this point so no actual syncing is happening
|
//at this point so no actual syncing is happening
|
||||||
val initSyncF = gen1F.flatMap(_ => spvNode.sync())
|
val initSyncF = gen1F.flatMap { _ =>
|
||||||
|
spvNode.sync()
|
||||||
|
}
|
||||||
|
|
||||||
//start generating a block every 10 seconds with bitcoind
|
//start generating a block every 10 seconds with bitcoind
|
||||||
//this should result in 5 blocks
|
//this should result in 5 blocks
|
||||||
|
@ -76,7 +80,8 @@ class SpvNodeTest extends NodeUnitTest {
|
||||||
//we should expect 5 headers have been announced to us via
|
//we should expect 5 headers have been announced to us via
|
||||||
//the send headers message.
|
//the send headers message.
|
||||||
val has6BlocksF = RpcUtil.retryUntilSatisfiedF(
|
val has6BlocksF = RpcUtil.retryUntilSatisfiedF(
|
||||||
conditionF = () => spvNode.chainApi.getBlockCount.map(_ == 6),
|
conditionF =
|
||||||
|
() => spvNode.chainApiFromDb().flatMap(_.getBlockCount.map(_ == 6)),
|
||||||
duration = 1.seconds)
|
duration = 1.seconds)
|
||||||
|
|
||||||
has6BlocksF.map(_ => succeed)
|
has6BlocksF.map(_ => succeed)
|
||||||
|
|
|
@ -88,7 +88,7 @@ class UpdateBloomFilterTest extends NodeUnitTest with BeforeAndAfter {
|
||||||
addressFromWallet <- wallet.getNewAddress()
|
addressFromWallet <- wallet.getNewAddress()
|
||||||
_ = addressFromWalletP.success(addressFromWallet)
|
_ = addressFromWalletP.success(addressFromWallet)
|
||||||
spv <- initSpv.start()
|
spv <- initSpv.start()
|
||||||
_ = spv.updateBloomFilter(addressFromWallet)
|
_ <- spv.updateBloomFilter(addressFromWallet)
|
||||||
_ <- spv.sync()
|
_ <- spv.sync()
|
||||||
_ <- rpc.sendToAddress(addressFromWallet, 1.bitcoin)
|
_ <- rpc.sendToAddress(addressFromWallet, 1.bitcoin)
|
||||||
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
||||||
|
@ -130,11 +130,11 @@ class UpdateBloomFilterTest extends NodeUnitTest with BeforeAndAfter {
|
||||||
5.bitcoin,
|
5.bitcoin,
|
||||||
SatoshisPerByte(100.sats))
|
SatoshisPerByte(100.sats))
|
||||||
_ = txFromWalletP.success(tx)
|
_ = txFromWalletP.success(tx)
|
||||||
spvNewBloom = spv.updateBloomFilter(tx)
|
updatedBloom <- spv.updateBloomFilter(tx).map(_.bloomFilter)
|
||||||
_ = spv.broadcastTransaction(tx)
|
_ = spv.broadcastTransaction(tx)
|
||||||
_ <- spv.sync()
|
_ <- spv.sync()
|
||||||
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
_ <- NodeTestUtil.awaitSync(spv, rpc)
|
||||||
_ = assert(spvNewBloom.bloomFilter.contains(tx.txId))
|
_ = assert(updatedBloom.contains(tx.txId))
|
||||||
_ = {
|
_ = {
|
||||||
cancelable = Some {
|
cancelable = Some {
|
||||||
system.scheduler.scheduleOnce(
|
system.scheduler.scheduleOnce(
|
||||||
|
|
|
@ -2,6 +2,8 @@ package org.bitcoins.node.networking
|
||||||
|
|
||||||
import akka.io.Tcp
|
import akka.io.Tcp
|
||||||
import akka.testkit.{TestActorRef, TestProbe}
|
import akka.testkit.{TestActorRef, TestProbe}
|
||||||
|
import org.bitcoins.chain.db.ChainDbManagement
|
||||||
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.Preconnection
|
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.Preconnection
|
||||||
|
@ -39,6 +41,7 @@ class P2PClientTest
|
||||||
BitcoinSTestAppConfig.getTestConfig()
|
BitcoinSTestAppConfig.getTestConfig()
|
||||||
implicit private val chainConf = config.chainConf
|
implicit private val chainConf = config.chainConf
|
||||||
implicit private val nodeConf = config.nodeConf
|
implicit private val nodeConf = config.nodeConf
|
||||||
|
implicit private val timeout = akka.util.Timeout(10.seconds)
|
||||||
|
|
||||||
implicit val np = config.chainConf.network
|
implicit val np = config.chainConf.network
|
||||||
|
|
||||||
|
@ -126,8 +129,20 @@ class P2PClientTest
|
||||||
}
|
}
|
||||||
behavior of "P2PClient"
|
behavior of "P2PClient"
|
||||||
|
|
||||||
|
override def beforeAll(): Unit = {
|
||||||
|
ChainDbManagement.createHeaderTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def afterAll(): Unit = {
|
||||||
|
ChainDbManagement.dropHeaderTable()
|
||||||
|
super.afterAll()
|
||||||
|
}
|
||||||
|
|
||||||
it must "establish a tcp connection with a bitcoin node" in {
|
it must "establish a tcp connection with a bitcoin node" in {
|
||||||
bitcoindPeerF.flatMap(remote => connectAndDisconnect(remote))
|
bitcoindPeerF.flatMap { remote =>
|
||||||
|
println(s"Starting test")
|
||||||
|
connectAndDisconnect(remote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "connect to two nodes" in {
|
it must "connect to two nodes" in {
|
||||||
|
@ -152,26 +167,32 @@ class P2PClientTest
|
||||||
def connectAndDisconnect(peer: Peer): Future[Assertion] = {
|
def connectAndDisconnect(peer: Peer): Future[Assertion] = {
|
||||||
val probe = TestProbe()
|
val probe = TestProbe()
|
||||||
val remote = peer.socket
|
val remote = peer.socket
|
||||||
val chainHandler = {
|
val peerMessageReceiverF =
|
||||||
val dao = BlockHeaderDAO()
|
PeerMessageReceiver.preConnection(peer, SpvNodeCallbacks.empty)
|
||||||
ChainHandler(dao)
|
|
||||||
|
val clientActorF: Future[TestActorRef[P2PClientActor]] =
|
||||||
|
peerMessageReceiverF.map { peerMsgRecv =>
|
||||||
|
TestActorRef(P2PClient.props(peer, peerMsgRecv), probe.ref)
|
||||||
|
}
|
||||||
|
val p2pClientF: Future[P2PClient] = clientActorF.map {
|
||||||
|
client: TestActorRef[P2PClientActor] =>
|
||||||
|
P2PClient(client, peer)
|
||||||
}
|
}
|
||||||
val peerMessageReceiver =
|
|
||||||
PeerMessageReceiver(state = Preconnection, chainHandler)
|
|
||||||
val client =
|
|
||||||
TestActorRef(P2PClient.props(peer, peerMessageReceiver), probe.ref)
|
|
||||||
|
|
||||||
client ! Tcp.Connect(remote)
|
val isConnectedF = for {
|
||||||
|
p2pClient <- p2pClientF
|
||||||
val isConnectedF =
|
_ = p2pClient.actor ! Tcp.Connect(remote)
|
||||||
TestAsyncUtil.retryUntilSatisfied(peerMessageReceiver.isInitialized)
|
isConnected <- TestAsyncUtil.retryUntilSatisfiedF(p2pClient.isConnected)
|
||||||
|
} yield isConnected
|
||||||
|
|
||||||
isConnectedF.flatMap { _ =>
|
isConnectedF.flatMap { _ =>
|
||||||
//disconnect here
|
val isDisconnectedF = for {
|
||||||
client ! Tcp.Abort
|
p2pClient <- p2pClientF
|
||||||
val isDisconnectedF =
|
_ = p2pClient.actor ! Tcp.Abort
|
||||||
TestAsyncUtil.retryUntilSatisfied(peerMessageReceiver.isDisconnected,
|
isDisconnected <- TestAsyncUtil.retryUntilSatisfiedF(
|
||||||
|
p2pClient.isDisconnected,
|
||||||
duration = 1.seconds)
|
duration = 1.seconds)
|
||||||
|
} yield isDisconnected
|
||||||
|
|
||||||
isDisconnectedF.map { _ =>
|
isDisconnectedF.map { _ =>
|
||||||
succeed
|
succeed
|
||||||
|
|
|
@ -22,32 +22,22 @@ class PeerMessageHandlerTest extends NodeUnitTest {
|
||||||
behavior of "PeerHandler"
|
behavior of "PeerHandler"
|
||||||
|
|
||||||
it must "be able to fully initialize a PeerMessageReceiver" in { _ =>
|
it must "be able to fully initialize a PeerMessageReceiver" in { _ =>
|
||||||
val peerHandlerF = bitcoindPeerF.map(p => NodeUnitTest.buildPeerHandler(p))
|
val peerHandlerF =
|
||||||
|
bitcoindPeerF.flatMap(p => NodeUnitTest.buildPeerHandler(p))
|
||||||
val peerMsgSenderF = peerHandlerF.map(_.peerMsgSender)
|
val peerMsgSenderF = peerHandlerF.map(_.peerMsgSender)
|
||||||
val peerMsgRecvF = peerHandlerF.map(_.peerMsgRecv)
|
val p2pClientF = peerHandlerF.map(_.p2pClient)
|
||||||
|
|
||||||
val _ =
|
val _ =
|
||||||
bitcoindPeerF.flatMap(p => peerHandlerF.map(_.peerMsgSender.connect()))
|
bitcoindPeerF.flatMap(p => peerHandlerF.map(_.peerMsgSender.connect()))
|
||||||
|
|
||||||
val isConnectedF = TestAsyncUtil.retryUntilSatisfiedF(
|
val isConnectedF = TestAsyncUtil.retryUntilSatisfiedF(
|
||||||
() => peerMsgRecvF.map(_.isConnected),
|
() => p2pClientF.flatMap(_.isConnected),
|
||||||
duration = 500.millis
|
duration = 500.millis
|
||||||
)
|
)
|
||||||
|
|
||||||
val hasVersionMsgF = isConnectedF.flatMap { _ =>
|
val isInitF = isConnectedF.flatMap { _ =>
|
||||||
TestAsyncUtil.retryUntilSatisfiedF(
|
TestAsyncUtil.retryUntilSatisfiedF(() =>
|
||||||
conditionF = () => peerMsgRecvF.map(_.hasReceivedVersionMsg)
|
p2pClientF.flatMap(_.isInitialized()))
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasVerackMsg = hasVersionMsgF.flatMap { _ =>
|
|
||||||
TestAsyncUtil.retryUntilSatisfiedF(
|
|
||||||
conditionF = () => peerMsgRecvF.map(_.hasReceivedVerackMsg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val isInitF = hasVerackMsg.flatMap { _ =>
|
|
||||||
peerMsgRecvF.map(p => assert(p.isInitialized))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val disconnectF = isInitF.flatMap { _ =>
|
val disconnectF = isInitF.flatMap { _ =>
|
||||||
|
@ -56,7 +46,7 @@ class PeerMessageHandlerTest extends NodeUnitTest {
|
||||||
|
|
||||||
val isDisconnectedF = disconnectF.flatMap { _ =>
|
val isDisconnectedF = disconnectF.flatMap { _ =>
|
||||||
TestAsyncUtil.retryUntilSatisfiedF(() =>
|
TestAsyncUtil.retryUntilSatisfiedF(() =>
|
||||||
peerMsgRecvF.map(_.isDisconnected))
|
p2pClientF.flatMap(_.isDisconnected()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,77 +2,116 @@ package org.bitcoins.node
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import org.bitcoins.chain.api.ChainApi
|
import org.bitcoins.chain.api.ChainApi
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
import org.bitcoins.core.bloom.BloomFilter
|
||||||
|
import org.bitcoins.core.p2p.NetworkPayload
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
|
import org.bitcoins.core.protocol.transaction.Transaction
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
|
import org.bitcoins.node.models.{
|
||||||
|
BroadcastAbleTransaction,
|
||||||
|
BroadcastAbleTransactionDAO,
|
||||||
|
Peer
|
||||||
|
}
|
||||||
import org.bitcoins.node.networking.P2PClient
|
import org.bitcoins.node.networking.P2PClient
|
||||||
import org.bitcoins.node.networking.peer.{
|
import org.bitcoins.node.networking.peer.{
|
||||||
PeerMessageReceiver,
|
PeerMessageReceiver,
|
||||||
PeerMessageSender
|
PeerMessageSender
|
||||||
}
|
}
|
||||||
import org.bitcoins.rpc.util.AsyncUtil
|
import org.bitcoins.rpc.util.AsyncUtil
|
||||||
|
import slick.jdbc.SQLiteProfile
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import org.bitcoins.core.bloom.BloomFilter
|
import scala.concurrent.duration.DurationInt
|
||||||
import org.bitcoins.core.p2p.NetworkPayload
|
import scala.util.{Failure, Success}
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
|
||||||
import org.bitcoins.node.models.BroadcastAbleTransaction
|
|
||||||
import org.bitcoins.node.models.BroadcastAbleTransactionDAO
|
|
||||||
import slick.jdbc.SQLiteProfile
|
|
||||||
import scala.util.Failure
|
|
||||||
import scala.util.Success
|
|
||||||
import org.bitcoins.db.P2PLogger
|
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
|
||||||
import org.bitcoins.core.protocol.BitcoinAddress
|
|
||||||
|
|
||||||
case class SpvNode(
|
case class SpvNode(
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
chainApi: ChainApi,
|
|
||||||
bloomFilter: BloomFilter,
|
bloomFilter: BloomFilter,
|
||||||
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty
|
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty
|
||||||
)(implicit system: ActorSystem, nodeAppConfig: NodeAppConfig)
|
)(
|
||||||
|
implicit system: ActorSystem,
|
||||||
|
nodeAppConfig: NodeAppConfig,
|
||||||
|
chainAppConfig: ChainAppConfig)
|
||||||
extends P2PLogger {
|
extends P2PLogger {
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
|
/** This implicit is required for using the [[akka.pattern.ask akka ask]]
|
||||||
|
* to query what the state of our node is, like [[isConnected isConnected]]
|
||||||
|
* */
|
||||||
|
implicit private val timeout = akka.util.Timeout(10.seconds)
|
||||||
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
||||||
|
|
||||||
private val peerMsgRecv =
|
/** This is constructing a chain api from disk every time we call this method
|
||||||
PeerMessageReceiver.newReceiver(chainApi, callbacks)
|
* This involves database calls which can be slow and expensive to construct
|
||||||
|
* our [[org.bitcoins.chain.blockchain.Blockchain Blockchain]]
|
||||||
|
* */
|
||||||
|
def chainApiFromDb(): Future[ChainApi] = {
|
||||||
|
ChainHandler.fromDatabase(BlockHeaderDAO())
|
||||||
|
}
|
||||||
|
|
||||||
private val client: P2PClient =
|
/** Unlike our chain api, this is cached inside our spv node
|
||||||
P2PClient(context = system, peer = peer, peerMessageReceiver = peerMsgRecv)
|
* object. Internally in [[org.bitcoins.node.networking.P2PClient p2p client]] you will see that
|
||||||
|
* the [[org.bitcoins.chain.api.ChainApi chain api]] is updated inside of the p2p client
|
||||||
|
* */
|
||||||
|
private val clientF: Future[P2PClient] = {
|
||||||
|
for {
|
||||||
|
chainApi <- chainApiFromDb()
|
||||||
|
} yield {
|
||||||
|
val peerMsgRecv: PeerMessageReceiver =
|
||||||
|
PeerMessageReceiver.newReceiver(chainApi = chainApi,
|
||||||
|
peer = peer,
|
||||||
|
callbacks = callbacks)
|
||||||
|
val p2p = P2PClient(context = system,
|
||||||
|
peer = peer,
|
||||||
|
peerMessageReceiver = peerMsgRecv)
|
||||||
|
p2p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val peerMsgSender: PeerMessageSender = {
|
private val peerMsgSenderF: Future[PeerMessageSender] = {
|
||||||
|
clientF.map { client =>
|
||||||
PeerMessageSender(client)
|
PeerMessageSender(client)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Updates our bloom filter to match the given TX
|
/** Updates our bloom filter to match the given TX
|
||||||
*
|
*
|
||||||
* @return SPV node with the updated bloom filter
|
* @return SPV node with the updated bloom filter
|
||||||
*/
|
*/
|
||||||
def updateBloomFilter(transaction: Transaction): SpvNode = {
|
def updateBloomFilter(transaction: Transaction): Future[SpvNode] = {
|
||||||
logger.info(s"Updating bloom filter with transaction=${transaction.txIdBE}")
|
logger(nodeAppConfig).info(
|
||||||
|
s"Updating bloom filter with transaction=${transaction.txIdBE}")
|
||||||
val newBloom = bloomFilter.update(transaction)
|
val newBloom = bloomFilter.update(transaction)
|
||||||
|
|
||||||
// we could send filteradd messages, but we would
|
// we could send filteradd messages, but we would
|
||||||
// then need to calculate all the new elements in
|
// then need to calculate all the new elements in
|
||||||
// the filter. this is easier:-)
|
// the filter. this is easier:-)
|
||||||
peerMsgSender.sendFilterClearMessage()
|
val newBloomLoadF = peerMsgSenderF.map { p =>
|
||||||
peerMsgSender.sendFilterLoadMessage(newBloom)
|
p.sendFilterClearMessage()
|
||||||
|
p.sendFilterLoadMessage(newBloom)
|
||||||
|
}
|
||||||
|
|
||||||
copy(bloomFilter = newBloom)
|
newBloomLoadF.map(_ => copy(bloomFilter = newBloom))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates our bloom filter to match the given address
|
/** Updates our bloom filter to match the given address
|
||||||
*
|
*
|
||||||
* @return SPV node with the updated bloom filter
|
* @return SPV node with the updated bloom filter
|
||||||
*/
|
*/
|
||||||
def updateBloomFilter(address: BitcoinAddress): SpvNode = {
|
def updateBloomFilter(address: BitcoinAddress): Future[SpvNode] = {
|
||||||
logger.info(s"Updating bloom filter with address=$address")
|
logger(nodeAppConfig).info(s"Updating bloom filter with address=$address")
|
||||||
val hash = address.hash
|
val hash = address.hash
|
||||||
val newBloom = bloomFilter.insert(hash)
|
val newBloom = bloomFilter.insert(hash)
|
||||||
peerMsgSender.sendFilterAddMessage(hash)
|
val sentFilterAddF = peerMsgSenderF.map(_.sendFilterAddMessage(hash))
|
||||||
|
|
||||||
|
sentFilterAddF.map { _ =>
|
||||||
copy(bloomFilter = newBloom)
|
copy(bloomFilter = newBloom)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the given P2P to our peer.
|
* Sends the given P2P to our peer.
|
||||||
|
@ -80,8 +119,8 @@ case class SpvNode(
|
||||||
* with P2P messages, therefore marked as
|
* with P2P messages, therefore marked as
|
||||||
* `private[node]`.
|
* `private[node]`.
|
||||||
*/
|
*/
|
||||||
private[node] def send(msg: NetworkPayload): Unit = {
|
private[node] def send(msg: NetworkPayload): Future[Unit] = {
|
||||||
peerMsgSender.sendMsg(msg)
|
peerMsgSenderF.map(_.sendMsg(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Starts our spv node */
|
/** Starts our spv node */
|
||||||
|
@ -89,59 +128,72 @@ case class SpvNode(
|
||||||
for {
|
for {
|
||||||
_ <- nodeAppConfig.initialize()
|
_ <- nodeAppConfig.initialize()
|
||||||
node <- {
|
node <- {
|
||||||
peerMsgSender.connect()
|
val isInitializedF = for {
|
||||||
|
_ <- peerMsgSenderF.map(_.connect())
|
||||||
|
_ <- AsyncUtil.retryUntilSatisfiedF(() => isInitialized)
|
||||||
|
} yield ()
|
||||||
|
|
||||||
val isInitializedF =
|
isInitializedF.failed.foreach(
|
||||||
AsyncUtil.retryUntilSatisfied(peerMsgRecv.isInitialized)
|
err =>
|
||||||
|
logger(nodeAppConfig).error(
|
||||||
isInitializedF.failed.foreach(err =>
|
s"Failed to connect with peer=$peer with err=${err}"))
|
||||||
logger.error(s"Failed to connect with peer=$peer with err=${err}"))
|
|
||||||
|
|
||||||
isInitializedF.map { _ =>
|
isInitializedF.map { _ =>
|
||||||
logger.info(s"Our peer=${peer} has been initialized")
|
logger(nodeAppConfig).info(s"Our peer=${peer} has been initialized")
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ <- peerMsgSenderF.map(_.sendFilterLoadMessage(bloomFilter))
|
||||||
} yield {
|
} yield {
|
||||||
logger.info(s"Sending bloomfilter=${bloomFilter.hex} to $peer")
|
logger(nodeAppConfig).info(
|
||||||
val _ = peerMsgSender.sendFilterLoadMessage(bloomFilter)
|
s"Sending bloomfilter=${bloomFilter.hex} to $peer")
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stops our spv node */
|
/** Stops our spv node */
|
||||||
def stop(): Future[SpvNode] = {
|
def stop(): Future[SpvNode] = {
|
||||||
peerMsgSender.disconnect()
|
logger(nodeAppConfig).info(s"Stopping spv node")
|
||||||
|
val disconnectF = peerMsgSenderF.map(_.disconnect())
|
||||||
|
|
||||||
val isStoppedF = AsyncUtil.retryUntilSatisfied(peerMsgRecv.isDisconnected)
|
val isStoppedF = disconnectF.flatMap { _ =>
|
||||||
|
logger(nodeAppConfig).info(s"Awaiting disconnect")
|
||||||
|
AsyncUtil.retryUntilSatisfiedF(() => isDisconnected)
|
||||||
|
}
|
||||||
|
|
||||||
isStoppedF.map(_ => this)
|
isStoppedF.map { _ =>
|
||||||
|
logger(nodeAppConfig).info(s"Spv node stopped!")
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Broadcasts the given transaction over the P2P network */
|
/** Broadcasts the given transaction over the P2P network */
|
||||||
def broadcastTransaction(transaction: Transaction): Unit = {
|
def broadcastTransaction(transaction: Transaction): Future[Unit] = {
|
||||||
val broadcastTx = BroadcastAbleTransaction(transaction)
|
val broadcastTx = BroadcastAbleTransaction(transaction)
|
||||||
|
|
||||||
txDAO.create(broadcastTx).onComplete {
|
txDAO.create(broadcastTx).onComplete {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
logger.error(s"Error when writing broadcastable TX to DB", exception)
|
logger(nodeAppConfig)
|
||||||
|
.error(s"Error when writing broadcastable TX to DB", exception)
|
||||||
case Success(written) =>
|
case Success(written) =>
|
||||||
logger.debug(
|
logger(nodeAppConfig).debug(
|
||||||
s"Wrote tx=${written.transaction.txIdBE} to broadcastable table")
|
s"Wrote tx=${written.transaction.txIdBE} to broadcastable table")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(s"Sending out inv for tx=${transaction.txIdBE}")
|
logger(nodeAppConfig).info(s"Sending out inv for tx=${transaction.txIdBE}")
|
||||||
peerMsgSender.sendInventoryMessage(transaction)
|
peerMsgSenderF.map(_.sendInventoryMessage(transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks if we have a tcp connection with our peer */
|
/** Checks if we have a tcp connection with our peer */
|
||||||
def isConnected: Boolean = peerMsgRecv.isConnected
|
def isConnected: Future[Boolean] = clientF.flatMap(_.isConnected)
|
||||||
|
|
||||||
/** Checks if we are fully initialized with our peer and have executed the handshake
|
/** Checks if we are fully initialized with our peer and have executed the handshake
|
||||||
* This means we can now send arbitrary messages to our peer
|
* This means we can now send arbitrary messages to our peer
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
def isInitialized: Boolean = peerMsgRecv.isInitialized
|
def isInitialized: Future[Boolean] = clientF.flatMap(_.isInitialized)
|
||||||
|
|
||||||
|
def isDisconnected: Future[Boolean] = clientF.flatMap(_.isDisconnected)
|
||||||
|
|
||||||
/** Starts to sync our spv node with our peer
|
/** Starts to sync our spv node with our peer
|
||||||
* If our local best block hash is the same as our peers
|
* If our local best block hash is the same as our peers
|
||||||
|
@ -151,13 +203,15 @@ case class SpvNode(
|
||||||
*/
|
*/
|
||||||
def sync(): Future[Unit] = {
|
def sync(): Future[Unit] = {
|
||||||
for {
|
for {
|
||||||
|
chainApi <- chainApiFromDb()
|
||||||
hash <- chainApi.getBestBlockHash
|
hash <- chainApi.getBestBlockHash
|
||||||
header <- chainApi
|
header <- chainApi
|
||||||
.getHeader(hash)
|
.getHeader(hash)
|
||||||
.map(_.get) // .get is safe since this is an internal call
|
.map(_.get) // .get is safe since this is an internal call
|
||||||
} yield {
|
} yield {
|
||||||
peerMsgSender.sendGetHeadersMessage(hash.flip)
|
peerMsgSenderF.map(_.sendGetHeadersMessage(hash.flip))
|
||||||
logger.info(s"Starting sync node, height=${header.height} hash=$hash")
|
logger(nodeAppConfig).info(
|
||||||
|
s"Starting sync node, height=${header.height} hash=$hash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,26 @@ package org.bitcoins.node.networking
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, ActorRefFactory, Props}
|
import akka.actor.{Actor, ActorRef, ActorRefFactory, Props}
|
||||||
import akka.io.{IO, Tcp}
|
import akka.io.{IO, Tcp}
|
||||||
import akka.util.ByteString
|
import akka.pattern.AskTimeoutException
|
||||||
|
import akka.util.{ByteString, CompactByteString, Timeout}
|
||||||
import org.bitcoins.core.config.NetworkParameters
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
import org.bitcoins.core.p2p.NetworkMessage
|
import org.bitcoins.core.p2p.NetworkMessage
|
||||||
import org.bitcoins.core.p2p.NetworkPayload
|
import org.bitcoins.core.p2p.NetworkPayload
|
||||||
|
import org.bitcoins.core.util.FutureUtil
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver.NetworkMessageReceived
|
import org.bitcoins.node.networking.peer.PeerMessageReceiver.NetworkMessageReceived
|
||||||
import org.bitcoins.node.util.BitcoinSpvNodeUtil
|
import org.bitcoins.node.util.BitcoinSpvNodeUtil
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import akka.util.CompactByteString
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util._
|
import scala.util._
|
||||||
import org.bitcoins.db.P2PLogger
|
import org.bitcoins.db.P2PLogger
|
||||||
|
|
||||||
|
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||||
|
import scala.concurrent.duration.DurationInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This actor is responsible for creating a connection,
|
* This actor is responsible for creating a connection,
|
||||||
* relaying messages and closing a connection to our peer on
|
* relaying messages and closing a connection to our peer on
|
||||||
|
@ -43,17 +48,19 @@ import org.bitcoins.db.P2PLogger
|
||||||
* CANNOT fit in a single TCP packet. This means we must cache
|
* CANNOT fit in a single TCP packet. This means we must cache
|
||||||
* the bytes and wait for the rest of them to be sent.
|
* the bytes and wait for the rest of them to be sent.
|
||||||
*
|
*
|
||||||
* @param peerMsgHandlerReceiver The place we send messages that we successfully parsed
|
* @param initPeerMsgHandlerReceiver The place we send messages that we successfully parsed
|
||||||
* from our peer on the P2P network. This is mostly likely
|
* from our peer on the P2P network. This is mostly likely
|
||||||
* a [[org.bitcoins.node.networking.peer.PeerMessageSender]]
|
* a [[org.bitcoins.node.networking.peer.PeerMessageSender]]
|
||||||
*/
|
*/
|
||||||
case class P2PClientActor(
|
case class P2PClientActor(
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
peerMsgHandlerReceiver: PeerMessageReceiver
|
initPeerMsgHandlerReceiver: PeerMessageReceiver
|
||||||
)(implicit config: NodeAppConfig)
|
)(implicit config: NodeAppConfig)
|
||||||
extends Actor
|
extends Actor
|
||||||
with P2PLogger {
|
with P2PLogger {
|
||||||
|
|
||||||
|
private var currentPeerMsgHandlerRecv = initPeerMsgHandlerReceiver
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manager is an actor that handles the underlying low level I/O resources (selectors, channels)
|
* The manager is an actor that handles the underlying low level I/O resources (selectors, channels)
|
||||||
* and instantiates workers for specific tasks, such as listening to incoming connections.
|
* and instantiates workers for specific tasks, such as listening to incoming connections.
|
||||||
|
@ -65,6 +72,8 @@ case class P2PClientActor(
|
||||||
*/
|
*/
|
||||||
val network: NetworkParameters = config.network
|
val network: NetworkParameters = config.network
|
||||||
|
|
||||||
|
private val timeout = 10.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: this comment seems wrong?
|
* TODO: this comment seems wrong?
|
||||||
*
|
*
|
||||||
|
@ -80,8 +89,12 @@ case class P2PClientActor(
|
||||||
self.forward(networkMsg)
|
self.forward(networkMsg)
|
||||||
case message: Tcp.Message =>
|
case message: Tcp.Message =>
|
||||||
val newUnalignedBytes =
|
val newUnalignedBytes =
|
||||||
handleTcpMessage(message, Some(peer), unalignedBytes)
|
Await.result(handleTcpMessage(message, Some(peer), unalignedBytes),
|
||||||
|
timeout)
|
||||||
context.become(awaitNetworkRequest(peer, newUnalignedBytes))
|
context.become(awaitNetworkRequest(peer, newUnalignedBytes))
|
||||||
|
|
||||||
|
case metaMsg: P2PClient.MetaMsg =>
|
||||||
|
sender ! handleMetaMsg(metaMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This context is responsible for initializing a tcp connection with a peer on the bitcoin p2p network */
|
/** This context is responsible for initializing a tcp connection with a peer on the bitcoin p2p network */
|
||||||
|
@ -92,16 +105,19 @@ case class P2PClientActor(
|
||||||
//after receiving Tcp.Connected we switch to the
|
//after receiving Tcp.Connected we switch to the
|
||||||
//'awaitNetworkRequest' context. This is the main
|
//'awaitNetworkRequest' context. This is the main
|
||||||
//execution loop for the Client actor
|
//execution loop for the Client actor
|
||||||
val _ = handleCommand(cmd, peer = None)
|
handleCommand(cmd, peer = None)
|
||||||
|
|
||||||
case connected: Tcp.Connected =>
|
case connected: Tcp.Connected =>
|
||||||
val _ = handleEvent(connected, unalignedBytes = ByteVector.empty)
|
Await.result(handleEvent(connected, unalignedBytes = ByteVector.empty),
|
||||||
|
timeout)
|
||||||
case msg: NetworkMessage =>
|
case msg: NetworkMessage =>
|
||||||
self.forward(msg.payload)
|
self.forward(msg.payload)
|
||||||
case payload: NetworkPayload =>
|
case payload: NetworkPayload =>
|
||||||
logger.error(
|
logger.error(
|
||||||
s"Cannot send a message to our peer when we are not connected! payload=${payload} peer=${peer}")
|
s"Cannot send a message to our peer when we are not connected! payload=${payload} peer=${peer}")
|
||||||
|
|
||||||
|
case metaMsg: P2PClient.MetaMsg =>
|
||||||
|
sender ! handleMetaMsg(metaMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,14 +128,14 @@ case class P2PClientActor(
|
||||||
private def handleTcpMessage(
|
private def handleTcpMessage(
|
||||||
message: Tcp.Message,
|
message: Tcp.Message,
|
||||||
peer: Option[ActorRef],
|
peer: Option[ActorRef],
|
||||||
unalignedBytes: ByteVector): ByteVector = {
|
unalignedBytes: ByteVector): Future[ByteVector] = {
|
||||||
message match {
|
message match {
|
||||||
case event: Tcp.Event =>
|
case event: Tcp.Event =>
|
||||||
handleEvent(event, unalignedBytes)
|
handleEvent(event, unalignedBytes = unalignedBytes)
|
||||||
case command: Tcp.Command =>
|
case command: Tcp.Command =>
|
||||||
handleCommand(command, peer)
|
handleCommand(command, peer)
|
||||||
|
|
||||||
unalignedBytes
|
Future.successful(unalignedBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,18 +144,19 @@ case class P2PClientActor(
|
||||||
*/
|
*/
|
||||||
private def handleEvent(
|
private def handleEvent(
|
||||||
event: Tcp.Event,
|
event: Tcp.Event,
|
||||||
unalignedBytes: ByteVector): ByteVector = {
|
unalignedBytes: ByteVector): Future[ByteVector] = {
|
||||||
|
import context.dispatcher
|
||||||
event match {
|
event match {
|
||||||
case Tcp.Bound(localAddress) =>
|
case Tcp.Bound(localAddress) =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Actor is now bound to the local address: ${localAddress}")
|
s"Actor is now bound to the local address: ${localAddress}")
|
||||||
context.parent ! Tcp.Bound(localAddress)
|
context.parent ! Tcp.Bound(localAddress)
|
||||||
|
|
||||||
unalignedBytes
|
Future.successful(unalignedBytes)
|
||||||
case Tcp.CommandFailed(command) =>
|
case Tcp.CommandFailed(command) =>
|
||||||
logger.debug(s"Client Command failed: ${command}")
|
logger.debug(s"Client Command failed: ${command}")
|
||||||
|
|
||||||
unalignedBytes
|
Future.successful(unalignedBytes)
|
||||||
case Tcp.Connected(remote, local) =>
|
case Tcp.Connected(remote, local) =>
|
||||||
logger.debug(s"Tcp connection to: ${remote}")
|
logger.debug(s"Tcp connection to: ${remote}")
|
||||||
logger.debug(s"Local: ${local}")
|
logger.debug(s"Local: ${local}")
|
||||||
|
@ -149,22 +166,30 @@ case class P2PClientActor(
|
||||||
//our bitcoin peer will send all messages to this actor.
|
//our bitcoin peer will send all messages to this actor.
|
||||||
sender ! Tcp.Register(self)
|
sender ! Tcp.Register(self)
|
||||||
|
|
||||||
val _ = peerMsgHandlerReceiver.connect(P2PClient(self, peer))
|
val newPeerMsgRecvF: Future[PeerMessageReceiver] =
|
||||||
|
currentPeerMsgHandlerRecv.connect(P2PClient(self, peer))
|
||||||
context.become(awaitNetworkRequest(sender, ByteVector.empty))
|
newPeerMsgRecvF.map { newPeerMsgRecv =>
|
||||||
|
currentPeerMsgHandlerRecv = newPeerMsgRecv
|
||||||
|
context.become(awaitNetworkRequest(sender, unalignedBytes))
|
||||||
unalignedBytes
|
unalignedBytes
|
||||||
|
}
|
||||||
|
|
||||||
case closeCmd @ (Tcp.ConfirmedClosed | Tcp.Closed | Tcp.Aborted |
|
case closeCmd @ (Tcp.ConfirmedClosed | Tcp.Closed | Tcp.Aborted |
|
||||||
Tcp.PeerClosed) =>
|
Tcp.PeerClosed) =>
|
||||||
logger.debug(s"Closed command received: ${closeCmd}")
|
logger.debug(s"Closed command received: ${closeCmd}")
|
||||||
|
|
||||||
//tell our peer message handler we are disconnecting
|
//tell our peer message handler we are disconnecting
|
||||||
val disconnectT = peerMsgHandlerReceiver.disconnect()
|
val newPeerMsgRecvF = currentPeerMsgHandlerRecv.disconnect()
|
||||||
|
|
||||||
disconnectT.failed.foreach(err =>
|
newPeerMsgRecvF.failed.foreach(err =>
|
||||||
logger.error(s"Failed to disconnect=${err}"))
|
logger.error(s"Failed to disconnect=${err}"))
|
||||||
|
|
||||||
|
newPeerMsgRecvF.map { newPeerMsgRecv =>
|
||||||
|
currentPeerMsgHandlerRecv = newPeerMsgRecv
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
unalignedBytes
|
unalignedBytes
|
||||||
|
}
|
||||||
|
|
||||||
case Tcp.Received(byteString: ByteString) =>
|
case Tcp.Received(byteString: ByteString) =>
|
||||||
val byteVec = ByteVector(byteString.toArray)
|
val byteVec = ByteVector(byteString.toArray)
|
||||||
logger.debug(s"Received ${byteVec.length} TCP bytes")
|
logger.debug(s"Received ${byteVec.length} TCP bytes")
|
||||||
|
@ -194,19 +219,27 @@ case class P2PClientActor(
|
||||||
logger.trace(s"Unaligned bytes: ${newUnalignedBytes.toHex}")
|
logger.trace(s"Unaligned bytes: ${newUnalignedBytes.toHex}")
|
||||||
}
|
}
|
||||||
|
|
||||||
//for the messages we successfully parsed above
|
val f: (
|
||||||
//send them to 'context.parent' -- this is the
|
PeerMessageReceiver,
|
||||||
//PeerMessageHandler that is responsible for
|
NetworkMessage) => Future[PeerMessageReceiver] = {
|
||||||
//creating this Client Actor
|
case (peerMsgRecv: PeerMessageReceiver, m: NetworkMessage) =>
|
||||||
messages.foreach { m =>
|
logger.trace(s"Processing message=${m}")
|
||||||
val msg = NetworkMessageReceived(m, P2PClient(self, peer))
|
val msg = NetworkMessageReceived(m, P2PClient(self, peer))
|
||||||
peerMsgHandlerReceiver.handleNetworkMessageReceived(msg)
|
val doneF = peerMsgRecv.handleNetworkMessageReceived(msg)
|
||||||
|
doneF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newMsgReceiverF: Future[PeerMessageReceiver] = {
|
||||||
|
logger.trace(s"About to process ${messages.length} messages")
|
||||||
|
FutureUtil.foldLeftAsync(currentPeerMsgHandlerRecv, messages)(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
newMsgReceiverF.map { newMsgReceiver =>
|
||||||
|
currentPeerMsgHandlerRecv = newMsgReceiver
|
||||||
newUnalignedBytes
|
newUnalignedBytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is responsible for handling a [[Tcp.Command]] algebraic data type
|
* This function is responsible for handling a [[Tcp.Command]] algebraic data type
|
||||||
|
@ -224,6 +257,17 @@ case class P2PClientActor(
|
||||||
manager ! bind
|
manager ! bind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of our peer given the [[P2PClient.MetaMsg meta message]]
|
||||||
|
*/
|
||||||
|
private def handleMetaMsg(metaMsg: P2PClient.MetaMsg): Boolean = {
|
||||||
|
metaMsg match {
|
||||||
|
case P2PClient.IsConnected => currentPeerMsgHandlerRecv.isConnected
|
||||||
|
case P2PClient.IsInitialized => currentPeerMsgHandlerRecv.isInitialized
|
||||||
|
case P2PClient.IsDisconnected => currentPeerMsgHandlerRecv.isDisconnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a network request to our peer on the network
|
* Sends a network request to our peer on the network
|
||||||
*/
|
*/
|
||||||
|
@ -237,10 +281,60 @@ case class P2PClientActor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2PClient(actor: ActorRef, peer: Peer)
|
case class P2PClient(actor: ActorRef, peer: Peer) extends P2PLogger {
|
||||||
|
import akka.pattern.ask
|
||||||
|
|
||||||
|
def isConnected()(
|
||||||
|
implicit timeout: Timeout,
|
||||||
|
ec: ExecutionContext): Future[Boolean] = {
|
||||||
|
val isConnectedF = actor.ask(P2PClient.IsConnected).mapTo[Boolean]
|
||||||
|
isConnectedF.recoverWith {
|
||||||
|
case _: Throwable => Future.successful(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isInitialized()(
|
||||||
|
implicit timeout: Timeout,
|
||||||
|
ec: ExecutionContext): Future[Boolean] = {
|
||||||
|
val isInitF = actor.ask(P2PClient.IsInitialized).mapTo[Boolean]
|
||||||
|
isInitF.recoverWith {
|
||||||
|
case _: Throwable => Future.successful(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isDisconnected()(
|
||||||
|
implicit timeout: Timeout,
|
||||||
|
ec: ExecutionContext): Future[Boolean] = {
|
||||||
|
val isDisconnect: Future[Boolean] =
|
||||||
|
actor.ask(P2PClient.IsDisconnected).mapTo[Boolean]
|
||||||
|
|
||||||
|
//this future can be failed, as we stop the P2PClientActor if we send a disconnect
|
||||||
|
//if that actor has been killed, the peer _has_ to have been disconnected
|
||||||
|
isDisconnect.recoverWith {
|
||||||
|
case _: Throwable => Future.successful(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object P2PClient extends P2PLogger {
|
object P2PClient extends P2PLogger {
|
||||||
|
|
||||||
|
/** A message hierarchy that canbe sent to [[P2PClientActor P2P Client Actor]]
|
||||||
|
* to query about meta information of a peer
|
||||||
|
* */
|
||||||
|
sealed trait MetaMsg
|
||||||
|
|
||||||
|
/** A message that can be sent to [[P2PClient p2p client]] that returns true
|
||||||
|
* if the peer is connected, false if not */
|
||||||
|
final case object IsConnected extends MetaMsg
|
||||||
|
|
||||||
|
/** A message that can be sent to [[P2PClient p2p client]] that returns true
|
||||||
|
* if the peer is initialized (p2p handshake complete), false if not */
|
||||||
|
final case object IsInitialized extends MetaMsg
|
||||||
|
|
||||||
|
/** A message that can be sent to [[P2PClient p2p client]] that returns true
|
||||||
|
* if the peer is disconnected, false otherwise */
|
||||||
|
final case object IsDisconnected extends MetaMsg
|
||||||
|
|
||||||
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver)(
|
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver)(
|
||||||
implicit config: NodeAppConfig
|
implicit config: NodeAppConfig
|
||||||
): Props =
|
): Props =
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
import org.bitcoins.chain.api.ChainApi
|
import org.bitcoins.chain.api.ChainApi
|
||||||
import org.bitcoins.core.util.FutureUtil
|
import org.bitcoins.core.p2p.{Inventory, MsgUnassigned, TypeIdentifier, _}
|
||||||
import org.bitcoins.core.p2p.{DataPayload, HeadersMessage, InventoryMessage}
|
import org.bitcoins.core.protocol.blockchain.{Block, MerkleBlock}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
|
||||||
import org.bitcoins.core.protocol.blockchain.Block
|
|
||||||
import org.bitcoins.core.protocol.blockchain.MerkleBlock
|
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
import org.bitcoins.core.protocol.transaction.Transaction
|
||||||
import org.bitcoins.core.p2p.BlockMessage
|
import org.bitcoins.db.P2PLogger
|
||||||
import org.bitcoins.core.p2p.TransactionMessage
|
|
||||||
import org.bitcoins.core.p2p.MerkleBlockMessage
|
|
||||||
import org.bitcoins.node.SpvNodeCallbacks
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
import org.bitcoins.core.p2p.GetDataMessage
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.node.models.BroadcastAbleTransactionDAO
|
import org.bitcoins.node.models.BroadcastAbleTransactionDAO
|
||||||
import slick.jdbc.SQLiteProfile
|
import slick.jdbc.SQLiteProfile
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
|
||||||
import org.bitcoins.core.p2p.TypeIdentifier
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import org.bitcoins.core.p2p.MsgUnassigned
|
|
||||||
import org.bitcoins.db.P2PLogger
|
|
||||||
import org.bitcoins.core.p2p.Inventory
|
|
||||||
|
|
||||||
/** This actor is meant to handle a [[org.bitcoins.core.p2p.DataPayload DataPayload]]
|
/** This actor is meant to handle a [[org.bitcoins.core.p2p.DataPayload DataPayload]]
|
||||||
* that a peer to sent to us on the p2p network, for instance, if we a receive a
|
* that a peer to sent to us on the p2p network, for instance, if we a receive a
|
||||||
* [[org.bitcoins.core.p2p.HeadersMessage HeadersMessage]] we should store those headers in our database
|
* [[org.bitcoins.core.p2p.HeadersMessage HeadersMessage]] we should store those headers in our database
|
||||||
*/
|
*/
|
||||||
class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
|
||||||
|
class DataMessageHandler(chainApi: ChainApi, callbacks: SpvNodeCallbacks)(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
appConfig: NodeAppConfig)
|
appConfig: NodeAppConfig)
|
||||||
extends P2PLogger {
|
extends P2PLogger {
|
||||||
|
@ -34,7 +26,7 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
|
|
||||||
def handleDataPayload(
|
def handleDataPayload(
|
||||||
payload: DataPayload,
|
payload: DataPayload,
|
||||||
peerMsgSender: PeerMessageSender): Future[Unit] = {
|
peerMsgSender: PeerMessageSender): Future[DataMessageHandler] = {
|
||||||
|
|
||||||
payload match {
|
payload match {
|
||||||
case getData: GetDataMessage =>
|
case getData: GetDataMessage =>
|
||||||
|
@ -65,17 +57,17 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
FutureUtil.unit
|
Future.successful(this)
|
||||||
case HeadersMessage(count, headers) =>
|
case HeadersMessage(count, headers) =>
|
||||||
logger.debug(s"Received headers message with ${count.toInt} headers")
|
logger.debug(s"Received headers message with ${count.toInt} headers")
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Received headers=${headers.map(_.hashBE.hex).mkString("[", ",", "]")}")
|
s"Received headers=${headers.map(_.hashBE.hex).mkString("[", ",", "]")}")
|
||||||
val chainApiF = chainHandler.processHeaders(headers)
|
val chainApiF = chainApi.processHeaders(headers)
|
||||||
|
|
||||||
logger.trace(s"Requesting data for headers=${headers.length}")
|
logger.trace(s"Requesting data for headers=${headers.length}")
|
||||||
peerMsgSender.sendGetDataMessage(headers: _*)
|
peerMsgSender.sendGetDataMessage(headers: _*)
|
||||||
|
|
||||||
chainApiF
|
val getHeadersF = chainApiF
|
||||||
.map { newApi =>
|
.map { newApi =>
|
||||||
if (headers.nonEmpty) {
|
if (headers.nonEmpty) {
|
||||||
|
|
||||||
|
@ -100,27 +92,40 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.failed
|
|
||||||
.map { err =>
|
getHeadersF.failed.map { err =>
|
||||||
logger.error(s"Error when processing headers message", err)
|
logger.error(s"Error when processing headers message", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
newApi <- chainApiF
|
||||||
|
_ <- getHeadersF
|
||||||
|
} yield {
|
||||||
|
new DataMessageHandler(newApi, callbacks)
|
||||||
|
}
|
||||||
case msg: BlockMessage =>
|
case msg: BlockMessage =>
|
||||||
Future { callbacks.onBlockReceived.foreach(_.apply(msg.block)) }
|
Future {
|
||||||
|
callbacks.onBlockReceived.foreach(_.apply(msg.block))
|
||||||
|
this
|
||||||
|
}
|
||||||
case TransactionMessage(tx) =>
|
case TransactionMessage(tx) =>
|
||||||
val belongsToMerkle =
|
val belongsToMerkle =
|
||||||
MerkleBuffers.putTx(tx, callbacks.onMerkleBlockReceived)
|
MerkleBuffers.putTx(tx, callbacks.onMerkleBlockReceived)
|
||||||
if (belongsToMerkle) {
|
if (belongsToMerkle) {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Transaction=${tx.txIdBE} belongs to merkleblock, not calling callbacks")
|
s"Transaction=${tx.txIdBE} belongs to merkleblock, not calling callbacks")
|
||||||
FutureUtil.unit
|
Future.successful(this)
|
||||||
} else {
|
} else {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Transaction=${tx.txIdBE} does not belong to merkleblock, processing given callbacks")
|
s"Transaction=${tx.txIdBE} does not belong to merkleblock, processing given callbacks")
|
||||||
Future { callbacks.onTxReceived.foreach(_.apply(tx)) }
|
Future {
|
||||||
|
callbacks.onTxReceived.foreach(_.apply(tx))
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case MerkleBlockMessage(merkleBlock) =>
|
case MerkleBlockMessage(merkleBlock) =>
|
||||||
MerkleBuffers.putMerkle(merkleBlock)
|
MerkleBuffers.putMerkle(merkleBlock)
|
||||||
FutureUtil.unit
|
Future.successful(this)
|
||||||
case invMsg: InventoryMessage =>
|
case invMsg: InventoryMessage =>
|
||||||
handleInventoryMsg(invMsg = invMsg, peerMsgSender = peerMsgSender)
|
handleInventoryMsg(invMsg = invMsg, peerMsgSender = peerMsgSender)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +133,7 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
|
|
||||||
private def handleInventoryMsg(
|
private def handleInventoryMsg(
|
||||||
invMsg: InventoryMessage,
|
invMsg: InventoryMessage,
|
||||||
peerMsgSender: PeerMessageSender): Future[Unit] = {
|
peerMsgSender: PeerMessageSender): Future[DataMessageHandler] = {
|
||||||
logger.info(s"Received inv=${invMsg}")
|
logger.info(s"Received inv=${invMsg}")
|
||||||
val getData = GetDataMessage(invMsg.inventories.map {
|
val getData = GetDataMessage(invMsg.inventories.map {
|
||||||
case Inventory(TypeIdentifier.MsgBlock, hash) =>
|
case Inventory(TypeIdentifier.MsgBlock, hash) =>
|
||||||
|
@ -136,7 +141,7 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
case other: Inventory => other
|
case other: Inventory => other
|
||||||
})
|
})
|
||||||
peerMsgSender.sendMsg(getData)
|
peerMsgSender.sendMsg(getData)
|
||||||
FutureUtil.unit
|
Future.successful(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
|
import org.bitcoins.node.networking.P2PClient
|
||||||
|
|
||||||
/*
|
/*
|
||||||
abstract class PeerHandler extends BitcoinSLogger {
|
abstract class PeerHandler extends BitcoinSLogger {
|
||||||
implicit val system: ActorSystem
|
implicit val system: ActorSystem
|
||||||
|
@ -58,6 +60,4 @@ object PeerHandler {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case class PeerHandler(
|
case class PeerHandler(p2pClient: P2PClient, peerMsgSender: PeerMessageSender)
|
||||||
peerMsgRecv: PeerMessageReceiver,
|
|
||||||
peerMsgSender: PeerMessageSender)
|
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
import akka.actor.ActorRefFactory
|
import akka.actor.ActorRefFactory
|
||||||
import org.bitcoins.core.p2p.NetworkMessage
|
import org.bitcoins.chain.api.ChainApi
|
||||||
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
import org.bitcoins.core.p2p.{NetworkMessage, _}
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.core.p2p._
|
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.P2PClient
|
import org.bitcoins.node.networking.P2PClient
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.{
|
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.{
|
||||||
|
@ -13,10 +18,7 @@ import org.bitcoins.node.networking.peer.PeerMessageReceiverState.{
|
||||||
Preconnection
|
Preconnection
|
||||||
}
|
}
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.concurrent.Future
|
||||||
import org.bitcoins.node.SpvNodeCallbacks
|
|
||||||
import org.bitcoins.db.P2PLogger
|
|
||||||
import org.bitcoins.chain.api.ChainApi
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for receiving messages from a peer on the
|
* Responsible for receiving messages from a peer on the
|
||||||
|
@ -26,62 +28,57 @@ import org.bitcoins.chain.api.ChainApi
|
||||||
* [[org.bitcoins.core.p2p.NetworkMessage NetworkMessage]]
|
* [[org.bitcoins.core.p2p.NetworkMessage NetworkMessage]]
|
||||||
*/
|
*/
|
||||||
class PeerMessageReceiver(
|
class PeerMessageReceiver(
|
||||||
state: PeerMessageReceiverState,
|
dataMessageHandler: DataMessageHandler,
|
||||||
callbacks: SpvNodeCallbacks,
|
val state: PeerMessageReceiverState,
|
||||||
chainHandler: ChainApi
|
peer: Peer,
|
||||||
)(implicit ref: ActorRefFactory, nodeAppConfig: NodeAppConfig)
|
callbacks: SpvNodeCallbacks
|
||||||
|
)(
|
||||||
|
implicit ref: ActorRefFactory,
|
||||||
|
nodeAppConfig: NodeAppConfig,
|
||||||
|
chainAppConfig: ChainAppConfig)
|
||||||
extends P2PLogger {
|
extends P2PLogger {
|
||||||
|
|
||||||
import ref.dispatcher
|
import ref.dispatcher
|
||||||
|
|
||||||
//TODO: Really bad to just modify this internal state
|
|
||||||
//not async safe at all
|
|
||||||
private var internalState: PeerMessageReceiverState = state
|
|
||||||
|
|
||||||
/** The peer we are connected to. */
|
|
||||||
private var peerOpt: Option[Peer] = None
|
|
||||||
|
|
||||||
/** This method is called when we have received
|
/** This method is called when we have received
|
||||||
* a [[akka.io.Tcp.Connected]] message from our peer
|
* a [[akka.io.Tcp.Connected]] message from our peer
|
||||||
* This means we have opened a Tcp connection,
|
* This means we have opened a Tcp connection,
|
||||||
* but have NOT started the handshake
|
* but have NOT started the handshake
|
||||||
* This method will initiate the handshake
|
* This method will initiate the handshake
|
||||||
*/
|
*/
|
||||||
protected[networking] def connect(client: P2PClient): Try[Unit] = {
|
protected[networking] def connect(
|
||||||
|
client: P2PClient): Future[PeerMessageReceiver] = {
|
||||||
|
|
||||||
internalState match {
|
state match {
|
||||||
case bad @ (_: Initializing | _: Normal | _: Disconnected) =>
|
case bad @ (_: Initializing | _: Normal | _: Disconnected) =>
|
||||||
Failure(
|
Future.failed(
|
||||||
new RuntimeException(s"Cannot call connect when in state=${bad}")
|
new RuntimeException(s"Cannot call connect when in state=${bad}")
|
||||||
)
|
)
|
||||||
case Preconnection =>
|
case Preconnection =>
|
||||||
peerOpt = Some(client.peer)
|
logger(nodeAppConfig).info(s"Connection established with peer=${peer}")
|
||||||
|
|
||||||
logger.info(s"Connection established with peer=${peerOpt.get}")
|
|
||||||
|
|
||||||
val newState = Preconnection.toInitializing(client)
|
val newState = Preconnection.toInitializing(client)
|
||||||
|
|
||||||
val _ = toState(newState)
|
|
||||||
|
|
||||||
val peerMsgSender = PeerMessageSender(client)
|
val peerMsgSender = PeerMessageSender(client)
|
||||||
|
|
||||||
peerMsgSender.sendVersionMessage()
|
peerMsgSender.sendVersionMessage()
|
||||||
|
|
||||||
Success(())
|
val newRecv = toState(newState)
|
||||||
|
|
||||||
|
Future.successful(newRecv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected[networking] def disconnect(): Try[Unit] = {
|
protected[networking] def disconnect(): Future[PeerMessageReceiver] = {
|
||||||
|
logger(nodeAppConfig).trace(s"Disconnecting with internalstate=${state}")
|
||||||
internalState match {
|
state match {
|
||||||
case bad @ (_: Initializing | _: Disconnected | Preconnection) =>
|
case bad @ (_: Initializing | _: Disconnected | Preconnection) =>
|
||||||
Failure(
|
Future.failed(
|
||||||
new RuntimeException(
|
new RuntimeException(
|
||||||
s"Cannot disconnect from peer=${peerOpt.get} when in state=${bad}")
|
s"Cannot disconnect from peer=${peer} when in state=${bad}")
|
||||||
)
|
)
|
||||||
|
|
||||||
case good: Normal =>
|
case good: Normal =>
|
||||||
logger.debug(s"Disconnected bitcoin peer=${peerOpt.get}")
|
logger(nodeAppConfig).debug(s"Disconnected bitcoin peer=${peer}")
|
||||||
val newState = Disconnected(
|
val newState = Disconnected(
|
||||||
clientConnectP = good.clientConnectP,
|
clientConnectP = good.clientConnectP,
|
||||||
clientDisconnectP = good.clientDisconnectP.success(()),
|
clientDisconnectP = good.clientDisconnectP.success(()),
|
||||||
|
@ -89,37 +86,40 @@ class PeerMessageReceiver(
|
||||||
verackMsgP = good.verackMsgP
|
verackMsgP = good.verackMsgP
|
||||||
)
|
)
|
||||||
|
|
||||||
val _ = toState(newState)
|
val newRecv = toState(newState)
|
||||||
Success(())
|
|
||||||
|
Future.successful(newRecv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def isConnected: Boolean = internalState.isConnected
|
private[networking] def isConnected: Boolean = state.isConnected
|
||||||
|
|
||||||
def isDisconnected: Boolean = internalState.isDisconnected
|
private[networking] def isDisconnected: Boolean = state.isDisconnected
|
||||||
|
|
||||||
def hasReceivedVersionMsg: Boolean =
|
private[networking] def hasReceivedVersionMsg: Boolean =
|
||||||
internalState.hasReceivedVersionMsg.isCompleted
|
state.hasReceivedVersionMsg.isCompleted
|
||||||
|
|
||||||
def hasReceivedVerackMsg: Boolean =
|
private[networking] def hasReceivedVerackMsg: Boolean =
|
||||||
internalState.hasReceivedVerackMsg.isCompleted
|
state.hasReceivedVerackMsg.isCompleted
|
||||||
|
|
||||||
def isInitialized: Boolean = internalState.isInitialized
|
private[networking] def isInitialized: Boolean = state.isInitialized
|
||||||
|
|
||||||
def handleNetworkMessageReceived(
|
def handleNetworkMessageReceived(
|
||||||
networkMsgRecv: PeerMessageReceiver.NetworkMessageReceived): Unit = {
|
networkMsgRecv: PeerMessageReceiver.NetworkMessageReceived): Future[
|
||||||
|
PeerMessageReceiver] = {
|
||||||
|
|
||||||
val client = networkMsgRecv.client
|
val client = networkMsgRecv.client
|
||||||
|
|
||||||
//create a way to send a response if we need too
|
//create a way to send a response if we need too
|
||||||
val peerMsgSender = PeerMessageSender(client)
|
val peerMsgSender = PeerMessageSender(client)
|
||||||
|
|
||||||
logger.debug(
|
logger(nodeAppConfig).debug(
|
||||||
s"Received message=${networkMsgRecv.msg.header.commandName} from peer=${client.peer} ")
|
s"Received message=${networkMsgRecv.msg.header.commandName} from peer=${client.peer} state=${state} ")
|
||||||
networkMsgRecv.msg.payload match {
|
networkMsgRecv.msg.payload match {
|
||||||
case controlPayload: ControlPayload =>
|
case controlPayload: ControlPayload =>
|
||||||
|
val peerMsgRecvF =
|
||||||
handleControlPayload(payload = controlPayload, sender = peerMsgSender)
|
handleControlPayload(payload = controlPayload, sender = peerMsgSender)
|
||||||
()
|
peerMsgRecvF
|
||||||
case dataPayload: DataPayload =>
|
case dataPayload: DataPayload =>
|
||||||
handleDataPayload(payload = dataPayload, sender = peerMsgSender)
|
handleDataPayload(payload = dataPayload, sender = peerMsgSender)
|
||||||
}
|
}
|
||||||
|
@ -135,12 +135,15 @@ class PeerMessageReceiver(
|
||||||
*/
|
*/
|
||||||
private def handleDataPayload(
|
private def handleDataPayload(
|
||||||
payload: DataPayload,
|
payload: DataPayload,
|
||||||
sender: PeerMessageSender): Unit = {
|
sender: PeerMessageSender): Future[PeerMessageReceiver] = {
|
||||||
val dataMsgHandler = new DataMessageHandler(callbacks, chainHandler)
|
|
||||||
//else it means we are receiving this data payload from a peer,
|
//else it means we are receiving this data payload from a peer,
|
||||||
//we need to handle it
|
//we need to handle it
|
||||||
dataMsgHandler.handleDataPayload(payload, sender)
|
val newDataMessageHandlerF =
|
||||||
()
|
dataMessageHandler.handleDataPayload(payload, sender)
|
||||||
|
|
||||||
|
newDataMessageHandlerF.map { handler =>
|
||||||
|
new PeerMessageReceiver(handler, state, peer, callbacks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,21 +155,21 @@ class PeerMessageReceiver(
|
||||||
*/
|
*/
|
||||||
private def handleControlPayload(
|
private def handleControlPayload(
|
||||||
payload: ControlPayload,
|
payload: ControlPayload,
|
||||||
sender: PeerMessageSender): Try[Unit] = {
|
sender: PeerMessageSender): Future[PeerMessageReceiver] = {
|
||||||
payload match {
|
payload match {
|
||||||
|
|
||||||
case versionMsg: VersionMessage =>
|
case versionMsg: VersionMessage =>
|
||||||
logger.trace(
|
logger(nodeAppConfig).trace(
|
||||||
s"Received versionMsg=${versionMsg}from peer=${peerOpt.get}")
|
s"Received versionMsg=${versionMsg}from peer=${peer}")
|
||||||
|
|
||||||
internalState match {
|
state match {
|
||||||
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
|
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
|
||||||
Failure(
|
Future.failed(
|
||||||
new RuntimeException(
|
new RuntimeException(
|
||||||
s"Cannot handle version message while in state=${bad}"))
|
s"Cannot handle version message while in state=${bad}"))
|
||||||
|
|
||||||
case good: Initializing =>
|
case good: Initializing =>
|
||||||
internalState = good.withVersionMsg(versionMsg)
|
val newState = good.withVersionMsg(versionMsg)
|
||||||
|
|
||||||
sender.sendVerackMessage()
|
sender.sendVerackMessage()
|
||||||
|
|
||||||
|
@ -174,45 +177,52 @@ class PeerMessageReceiver(
|
||||||
//we don't want to have to request them manually
|
//we don't want to have to request them manually
|
||||||
sender.sendHeadersMessage()
|
sender.sendHeadersMessage()
|
||||||
|
|
||||||
Success(())
|
val newRecv = toState(newState)
|
||||||
|
|
||||||
|
Future.successful(newRecv)
|
||||||
}
|
}
|
||||||
|
|
||||||
case VerAckMessage =>
|
case VerAckMessage =>
|
||||||
internalState match {
|
state match {
|
||||||
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
|
case bad @ (_: Disconnected | _: Normal | Preconnection) =>
|
||||||
Failure(
|
Future.failed(
|
||||||
new RuntimeException(
|
new RuntimeException(
|
||||||
s"Cannot handle version message while in state=${bad}"))
|
s"Cannot handle version message while in state=${bad}"))
|
||||||
|
|
||||||
case good: Initializing =>
|
case good: Initializing =>
|
||||||
internalState = good.toNormal(VerAckMessage)
|
val newState = good.toNormal(VerAckMessage)
|
||||||
Success(())
|
val newRecv = toState(newState)
|
||||||
|
Future.successful(newRecv)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ping: PingMessage =>
|
case ping: PingMessage =>
|
||||||
sender.sendPong(ping)
|
sender.sendPong(ping)
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case SendHeadersMessage =>
|
case SendHeadersMessage =>
|
||||||
//not implemented as of now
|
//not implemented as of now
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case _: AddrMessage =>
|
case _: AddrMessage =>
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case _ @(_: FilterAddMessage | _: FilterLoadMessage |
|
case _ @(_: FilterAddMessage | _: FilterLoadMessage |
|
||||||
FilterClearMessage) =>
|
FilterClearMessage) =>
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case _ @(GetAddrMessage | _: PongMessage) =>
|
case _ @(GetAddrMessage | _: PongMessage) =>
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case _: RejectMessage =>
|
case _: RejectMessage =>
|
||||||
Success(())
|
Future.successful(this)
|
||||||
case _: FeeFilterMessage =>
|
case _: FeeFilterMessage =>
|
||||||
Success(())
|
Future.successful(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toState(state: PeerMessageReceiverState): Unit = {
|
/** Transitions our PeerMessageReceiver to a new state */
|
||||||
logger.debug(
|
def toState(newState: PeerMessageReceiverState): PeerMessageReceiver = {
|
||||||
s"PeerMessageReceiver changing state, oldState=$internalState, newState=$state")
|
new PeerMessageReceiver(
|
||||||
internalState = state
|
dataMessageHandler = dataMessageHandler,
|
||||||
|
state = newState,
|
||||||
|
peer = peer,
|
||||||
|
callbacks = callbacks
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,20 +241,52 @@ object PeerMessageReceiver {
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
state: PeerMessageReceiverState,
|
state: PeerMessageReceiverState,
|
||||||
chainHandler: ChainApi,
|
chainApi: ChainApi,
|
||||||
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
peer: Peer,
|
||||||
|
callbacks: SpvNodeCallbacks)(
|
||||||
implicit ref: ActorRefFactory,
|
implicit ref: ActorRefFactory,
|
||||||
nodeAppConfig: NodeAppConfig): PeerMessageReceiver = {
|
nodeAppConfig: NodeAppConfig,
|
||||||
new PeerMessageReceiver(state, callbacks, chainHandler)
|
chainAppConfig: ChainAppConfig
|
||||||
|
): PeerMessageReceiver = {
|
||||||
|
import ref.dispatcher
|
||||||
|
val dataHandler = new DataMessageHandler(chainApi, callbacks)
|
||||||
|
new PeerMessageReceiver(dataMessageHandler = dataHandler,
|
||||||
|
state = state,
|
||||||
|
peer = peer,
|
||||||
|
callbacks = callbacks)
|
||||||
}
|
}
|
||||||
|
|
||||||
def newReceiver(
|
/**
|
||||||
chainHandler: ChainApi,
|
* Creates a peer message receiver that is ready
|
||||||
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
* to be connected to a peer. This can be given to [[org.bitcoins.node.networking.P2PClient.props() P2PClient]]
|
||||||
|
* to connect to a peer on the network
|
||||||
|
*/
|
||||||
|
def preConnection(peer: Peer, callbacks: SpvNodeCallbacks)(
|
||||||
|
implicit ref: ActorRefFactory,
|
||||||
|
nodeAppConfig: NodeAppConfig,
|
||||||
|
chainAppConfig: ChainAppConfig
|
||||||
|
): Future[PeerMessageReceiver] = {
|
||||||
|
import ref.dispatcher
|
||||||
|
val blockHeaderDAO = BlockHeaderDAO()
|
||||||
|
val chainHandlerF =
|
||||||
|
ChainHandler.fromDatabase(blockHeaderDAO)
|
||||||
|
for {
|
||||||
|
chainHandler <- chainHandlerF
|
||||||
|
} yield {
|
||||||
|
PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||||
|
chainApi = chainHandler,
|
||||||
|
peer = peer,
|
||||||
|
callbacks = callbacks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def newReceiver(chainApi: ChainApi, peer: Peer, callbacks: SpvNodeCallbacks)(
|
||||||
implicit nodeAppConfig: NodeAppConfig,
|
implicit nodeAppConfig: NodeAppConfig,
|
||||||
|
chainAppConfig: ChainAppConfig,
|
||||||
ref: ActorRefFactory): PeerMessageReceiver = {
|
ref: ActorRefFactory): PeerMessageReceiver = {
|
||||||
new PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||||
callbacks,
|
chainApi = chainApi,
|
||||||
chainHandler)
|
peer = peer,
|
||||||
|
callbacks = callbacks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,8 @@ trait ChainUnitTest
|
||||||
def createPopulatedChainHandler(): Future[ChainHandler] = {
|
def createPopulatedChainHandler(): Future[ChainHandler] = {
|
||||||
for {
|
for {
|
||||||
blockHeaderDAO <- ChainUnitTest.createPopulatedBlockHeaderDAO()
|
blockHeaderDAO <- ChainUnitTest.createPopulatedBlockHeaderDAO()
|
||||||
} yield ChainHandler(blockHeaderDAO = blockHeaderDAO)
|
chainHandler <- ChainHandler.fromDatabase(blockHeaderDAO = blockHeaderDAO)
|
||||||
|
} yield chainHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
def withPopulatedChainHandler(test: OneArgAsyncTest): FutureOutcome = {
|
def withPopulatedChainHandler(test: OneArgAsyncTest): FutureOutcome = {
|
||||||
|
@ -194,15 +195,17 @@ trait ChainUnitTest
|
||||||
|
|
||||||
def createChainHandlerWithBitcoindZmq(
|
def createChainHandlerWithBitcoindZmq(
|
||||||
bitcoind: BitcoindRpcClient): Future[(ChainHandler, ZMQSubscriber)] = {
|
bitcoind: BitcoindRpcClient): Future[(ChainHandler, ZMQSubscriber)] = {
|
||||||
val (chainHandler, genesisHeaderF) =
|
val handlerWithGenesisHeaderF =
|
||||||
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
||||||
|
|
||||||
|
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
|
||||||
|
|
||||||
val zmqRawBlockUriOpt: Option[InetSocketAddress] =
|
val zmqRawBlockUriOpt: Option[InetSocketAddress] =
|
||||||
bitcoind.instance.zmqConfig.rawBlock
|
bitcoind.instance.zmqConfig.rawBlock
|
||||||
|
|
||||||
val handleRawBlock: ByteVector => Unit = { bytes: ByteVector =>
|
val handleRawBlock: ByteVector => Unit = { bytes: ByteVector =>
|
||||||
val block = Block.fromBytes(bytes)
|
val block = Block.fromBytes(bytes)
|
||||||
chainHandler.processHeader(block.blockHeader)
|
chainHandlerF.flatMap(_.processHeader(block.blockHeader))
|
||||||
|
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
@ -217,15 +220,19 @@ trait ChainUnitTest
|
||||||
zmqSubscriber.start()
|
zmqSubscriber.start()
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
||||||
genesisHeaderF.map(_ => (chainHandler, zmqSubscriber))
|
for {
|
||||||
|
chainHandler <- chainHandlerF
|
||||||
|
} yield (chainHandler, zmqSubscriber)
|
||||||
}
|
}
|
||||||
|
|
||||||
def createChainApiWithBitcoindRpc(
|
def createChainApiWithBitcoindRpc(
|
||||||
bitcoind: BitcoindRpcClient): Future[BitcoindChainHandlerViaRpc] = {
|
bitcoind: BitcoindRpcClient): Future[BitcoindChainHandlerViaRpc] = {
|
||||||
val (handler, genesisHeaderF) =
|
val handlerWithGenesisHeaderF =
|
||||||
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
||||||
|
|
||||||
genesisHeaderF.map { _ =>
|
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
|
||||||
|
|
||||||
|
chainHandlerF.map { handler =>
|
||||||
chain.fixture.BitcoindChainHandlerViaRpc(bitcoind, handler)
|
chain.fixture.BitcoindChainHandlerViaRpc(bitcoind, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,16 +311,21 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||||
def createChainHandler()(
|
def createChainHandler()(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
appConfig: ChainAppConfig): Future[ChainHandler] = {
|
appConfig: ChainAppConfig): Future[ChainHandler] = {
|
||||||
val (chainHandler, genesisHeaderF) = setupHeaderTableWithGenesisHeader()
|
val handlerWithGenesisHeaderF =
|
||||||
genesisHeaderF.map(_ => chainHandler)
|
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
||||||
|
|
||||||
|
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
|
||||||
|
chainHandlerF
|
||||||
}
|
}
|
||||||
|
|
||||||
def createBlockHeaderDAO()(
|
def createBlockHeaderDAO()(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
appConfig: ChainAppConfig): Future[BlockHeaderDAO] = {
|
appConfig: ChainAppConfig): Future[BlockHeaderDAO] = {
|
||||||
val (chainHandler, genesisHeaderF) = setupHeaderTableWithGenesisHeader()
|
val handlerWithGenesisHeaderF =
|
||||||
|
ChainUnitTest.setupHeaderTableWithGenesisHeader()
|
||||||
|
|
||||||
genesisHeaderF.map(_ => chainHandler.blockHeaderDAO)
|
val chainHandlerF = handlerWithGenesisHeaderF.map(_._1)
|
||||||
|
chainHandlerF.map(_.blockHeaderDAO)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates and populates BlockHeaderTable with block headers 562375 to 571375 */
|
/** Creates and populates BlockHeaderTable with block headers 562375 to 571375 */
|
||||||
|
@ -365,17 +377,21 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||||
dbHeaders = dbHeaders,
|
dbHeaders = dbHeaders,
|
||||||
batchesSoFar = Vector.empty)
|
batchesSoFar = Vector.empty)
|
||||||
|
|
||||||
val chainHandler = ChainUnitTest.makeChainHandler()
|
val chainHandlerF = ChainUnitTest.makeChainHandler()
|
||||||
|
|
||||||
val insertedF = tableSetupF.flatMap { _ =>
|
val insertedF = tableSetupF.flatMap { _ =>
|
||||||
batchedDbHeaders.foldLeft(
|
batchedDbHeaders.foldLeft(
|
||||||
Future.successful[Vector[BlockHeaderDb]](Vector.empty)) {
|
Future.successful[Vector[BlockHeaderDb]](Vector.empty)) {
|
||||||
case (fut, batch) =>
|
case (fut, batch) =>
|
||||||
fut.flatMap(_ => chainHandler.blockHeaderDAO.createAll(batch))
|
for {
|
||||||
|
_ <- fut
|
||||||
|
chainHandler <- chainHandlerF
|
||||||
|
headers <- chainHandler.blockHeaderDAO.createAll(batch)
|
||||||
|
} yield headers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertedF.map(_ => chainHandler.blockHeaderDAO)
|
insertedF.flatMap(_ => chainHandlerF.map(_.blockHeaderDAO))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,24 +414,31 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||||
/** Creates the [[org.bitcoins.chain.models.BlockHeaderTable]] and inserts the genesis header */
|
/** Creates the [[org.bitcoins.chain.models.BlockHeaderTable]] and inserts the genesis header */
|
||||||
def setupHeaderTableWithGenesisHeader()(
|
def setupHeaderTableWithGenesisHeader()(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
appConfig: ChainAppConfig): (ChainHandler, Future[BlockHeaderDb]) = {
|
appConfig: ChainAppConfig): Future[(ChainHandler, BlockHeaderDb)] = {
|
||||||
val tableSetupF = setupHeaderTable()
|
val tableSetupF = setupHeaderTable()
|
||||||
|
|
||||||
val chainHandler = makeChainHandler()
|
val chainHandlerF = makeChainHandler()
|
||||||
|
|
||||||
val genesisHeaderF = tableSetupF.flatMap { _ =>
|
val genesisHeaderF = tableSetupF.flatMap { _ =>
|
||||||
chainHandler.blockHeaderDAO.create(genesisHeaderDb)
|
for {
|
||||||
|
chainHandler <- chainHandlerF
|
||||||
|
genHeader <- chainHandler.blockHeaderDAO.create(genesisHeaderDb)
|
||||||
|
} yield genHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
(chainHandler, genesisHeaderF)
|
for {
|
||||||
|
genHeader <- genesisHeaderF
|
||||||
|
chainHandler <- makeChainHandler()
|
||||||
|
} yield (chainHandler, genHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
def makeChainHandler()(
|
def makeChainHandler()(
|
||||||
implicit appConfig: ChainAppConfig,
|
implicit appConfig: ChainAppConfig,
|
||||||
ec: ExecutionContext): ChainHandler = {
|
ec: ExecutionContext): Future[ChainHandler] = {
|
||||||
lazy val blockHeaderDAO = BlockHeaderDAO()
|
lazy val blockHeaderDAO = BlockHeaderDAO()
|
||||||
|
|
||||||
ChainHandler(blockHeaderDAO)
|
ChainHandler.fromDatabase(blockHeaderDAO = blockHeaderDAO)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,9 +107,9 @@ abstract class NodeTestUtil extends BitcoinSLogger {
|
||||||
def isSameBestHash(node: SpvNode, rpc: BitcoindRpcClient)(
|
def isSameBestHash(node: SpvNode, rpc: BitcoindRpcClient)(
|
||||||
implicit ec: ExecutionContext): Future[Boolean] = {
|
implicit ec: ExecutionContext): Future[Boolean] = {
|
||||||
val hashF = rpc.getBestBlockHash
|
val hashF = rpc.getBestBlockHash
|
||||||
val spvHashF = node.chainApi.getBestBlockHash
|
|
||||||
for {
|
for {
|
||||||
spvBestHash <- spvHashF
|
chainApi <- node.chainApiFromDb()
|
||||||
|
spvBestHash <- chainApi.getBestBlockHash
|
||||||
hash <- hashF
|
hash <- hashF
|
||||||
} yield {
|
} yield {
|
||||||
spvBestHash == hash
|
spvBestHash == hash
|
||||||
|
@ -122,9 +122,8 @@ abstract class NodeTestUtil extends BitcoinSLogger {
|
||||||
def isSameBlockCount(spv: SpvNode, rpc: BitcoindRpcClient)(
|
def isSameBlockCount(spv: SpvNode, rpc: BitcoindRpcClient)(
|
||||||
implicit ec: ExecutionContext): Future[Boolean] = {
|
implicit ec: ExecutionContext): Future[Boolean] = {
|
||||||
val rpcCountF = rpc.getBlockCount
|
val rpcCountF = rpc.getBlockCount
|
||||||
val spvCountF = spv.chainApi.getBlockCount
|
|
||||||
for {
|
for {
|
||||||
spvCount <- spvCountF
|
spvCount <- spv.chainApiFromDb().flatMap(_.getBlockCount)
|
||||||
rpcCount <- rpcCountF
|
rpcCount <- rpcCountF
|
||||||
} yield rpcCount == spvCount
|
} yield rpcCount == spvCount
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import akka.actor.ActorSystem
|
||||||
import org.bitcoins.chain.blockchain.ChainHandler
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
import org.bitcoins.chain.api.ChainApi
|
||||||
import org.bitcoins.core.config.NetworkParameters
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
import org.bitcoins.db.AppConfig
|
import org.bitcoins.db.AppConfig
|
||||||
|
@ -15,6 +16,7 @@ import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.peer.{
|
import org.bitcoins.node.networking.peer.{
|
||||||
PeerHandler,
|
PeerHandler,
|
||||||
PeerMessageReceiver,
|
PeerMessageReceiver,
|
||||||
|
PeerMessageReceiverState,
|
||||||
PeerMessageSender
|
PeerMessageSender
|
||||||
}
|
}
|
||||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
|
@ -154,6 +156,38 @@ object NodeUnitTest extends BitcoinSLogger {
|
||||||
wallet: UnlockedWalletApi,
|
wallet: UnlockedWalletApi,
|
||||||
bitcoindRpc: BitcoindRpcClient)
|
bitcoindRpc: BitcoindRpcClient)
|
||||||
|
|
||||||
|
def buildPeerMessageReceiver(chainApi: ChainApi, peer: Peer)(
|
||||||
|
implicit appConfig: BitcoinSAppConfig,
|
||||||
|
system: ActorSystem): Future[PeerMessageReceiver] = {
|
||||||
|
val receiver =
|
||||||
|
PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||||
|
chainApi = chainApi,
|
||||||
|
peer = peer,
|
||||||
|
callbacks = SpvNodeCallbacks.empty)
|
||||||
|
Future.successful(receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
def buildPeerHandler(peer: Peer)(
|
||||||
|
implicit nodeAppConfig: NodeAppConfig,
|
||||||
|
chainAppConfig: ChainAppConfig,
|
||||||
|
system: ActorSystem): Future[PeerHandler] = {
|
||||||
|
import system.dispatcher
|
||||||
|
val chainApiF = ChainUnitTest.createChainHandler()
|
||||||
|
val peerMsgReceiverF = chainApiF.flatMap { _ =>
|
||||||
|
PeerMessageReceiver.preConnection(peer, SpvNodeCallbacks.empty)
|
||||||
|
}
|
||||||
|
//the problem here is the 'self', this needs to be an ordinary peer message handler
|
||||||
|
//that can handle the handshake
|
||||||
|
val peerHandlerF = for {
|
||||||
|
peerMsgReceiver <- peerMsgReceiverF
|
||||||
|
client = NodeTestUtil.client(peer, peerMsgReceiver)
|
||||||
|
peerMsgSender = PeerMessageSender(client)
|
||||||
|
} yield PeerHandler(client, peerMsgSender)
|
||||||
|
|
||||||
|
peerHandlerF
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
def destroySpvNode(spvNode: SpvNode)(
|
def destroySpvNode(spvNode: SpvNode)(
|
||||||
implicit config: BitcoinSAppConfig,
|
implicit config: BitcoinSAppConfig,
|
||||||
ec: ExecutionContext): Future[Unit] = {
|
ec: ExecutionContext): Future[Unit] = {
|
||||||
|
@ -165,6 +199,7 @@ object NodeUnitTest extends BitcoinSLogger {
|
||||||
spvNodeConnectedWithBitcoind: SpvNodeConnectedWithBitcoind)(
|
spvNodeConnectedWithBitcoind: SpvNodeConnectedWithBitcoind)(
|
||||||
implicit system: ActorSystem,
|
implicit system: ActorSystem,
|
||||||
appConfig: BitcoinSAppConfig): Future[Unit] = {
|
appConfig: BitcoinSAppConfig): Future[Unit] = {
|
||||||
|
logger.debug(s"Beggining tear down of spv node connected with bitcoind")
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
val spvNode = spvNodeConnectedWithBitcoind.spvNode
|
val spvNode = spvNodeConnectedWithBitcoind.spvNode
|
||||||
val bitcoind = spvNodeConnectedWithBitcoind.bitcoind
|
val bitcoind = spvNodeConnectedWithBitcoind.bitcoind
|
||||||
|
@ -174,7 +209,10 @@ object NodeUnitTest extends BitcoinSLogger {
|
||||||
for {
|
for {
|
||||||
_ <- spvNodeDestroyF
|
_ <- spvNodeDestroyF
|
||||||
_ <- bitcoindDestroyF
|
_ <- bitcoindDestroyF
|
||||||
} yield ()
|
} yield {
|
||||||
|
logger.debug(s"Done with teardown of spv node connected with bitcoind!")
|
||||||
|
()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a spv node, a funded bitcoin-s wallet, all of which are connected to bitcoind */
|
/** Creates a spv node, a funded bitcoin-s wallet, all of which are connected to bitcoind */
|
||||||
|
@ -211,31 +249,16 @@ object NodeUnitTest extends BitcoinSLogger {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def buildPeerMessageReceiver()(
|
def buildPeerMessageReceiver(chainApi: ChainApi, peer: Peer)(
|
||||||
implicit system: ActorSystem,
|
implicit nodeAppConfig: NodeAppConfig,
|
||||||
chainAppConfig: ChainAppConfig,
|
chainAppConfig: ChainAppConfig,
|
||||||
nodeAppConfig: NodeAppConfig): PeerMessageReceiver = {
|
system: ActorSystem): Future[PeerMessageReceiver] = {
|
||||||
import system.dispatcher
|
|
||||||
val dao = BlockHeaderDAO()
|
|
||||||
val chainHandler = ChainHandler(dao)
|
|
||||||
val receiver =
|
val receiver =
|
||||||
PeerMessageReceiver.newReceiver(chainHandler, SpvNodeCallbacks.empty)
|
PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||||
receiver
|
chainApi = chainApi,
|
||||||
}
|
peer = peer,
|
||||||
|
callbacks = SpvNodeCallbacks.empty)
|
||||||
def buildPeerHandler(peer: Peer)(
|
Future.successful(receiver)
|
||||||
implicit system: ActorSystem,
|
|
||||||
chainAppConfig: ChainAppConfig,
|
|
||||||
nodeAppConfig: NodeAppConfig): PeerHandler = {
|
|
||||||
val peerMsgReceiver = buildPeerMessageReceiver()
|
|
||||||
//the problem here is the 'self', this needs to be an ordinary peer message handler
|
|
||||||
//that can handle the handshake
|
|
||||||
val peerMsgSender: PeerMessageSender = {
|
|
||||||
val client = NodeTestUtil.client(peer, peerMsgReceiver)
|
|
||||||
PeerMessageSender(client)
|
|
||||||
}
|
|
||||||
PeerHandler(peerMsgReceiver, peerMsgSender)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def peerSocketAddress(
|
def peerSocketAddress(
|
||||||
|
@ -256,10 +279,9 @@ object NodeUnitTest extends BitcoinSLogger {
|
||||||
val chainApiF = ChainUnitTest.createChainHandler()
|
val chainApiF = ChainUnitTest.createChainHandler()
|
||||||
val peer = createPeer(bitcoind)
|
val peer = createPeer(bitcoind)
|
||||||
for {
|
for {
|
||||||
chainApi <- chainApiF
|
_ <- chainApiF
|
||||||
} yield {
|
} yield {
|
||||||
SpvNode(peer = peer,
|
SpvNode(peer = peer,
|
||||||
chainApi = chainApi,
|
|
||||||
bloomFilter = NodeTestUtil.emptyBloomFilter,
|
bloomFilter = NodeTestUtil.emptyBloomFilter,
|
||||||
callbacks = callbacks)
|
callbacks = callbacks)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue