diff --git a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/BlockchainTest.scala b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/BlockchainTest.scala index a0a8726103..37aad1a1d0 100644 --- a/chain-test/src/test/scala/org/bitcoins/chain/blockchain/BlockchainTest.scala +++ b/chain-test/src/test/scala/org/bitcoins/chain/blockchain/BlockchainTest.scala @@ -25,11 +25,10 @@ class BlockchainTest extends ChainUnitTest { val newHeader = BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb) - val connectTipF = Blockchain.connectTip(header = newHeader.blockHeader, - blockHeaderDAO = bhDAO, - Vector(blockchain)) + val connectTip = Blockchain.connectTip(header = newHeader.blockHeader, + Vector(blockchain)) - connectTipF.map { + connectTip match { case BlockchainUpdate.Successful(_, connectedHeader) => assert(newHeader == connectedHeader) diff --git a/chain-test/src/test/scala/org/bitcoins/chain/pow/BitcoinPowTest.scala b/chain-test/src/test/scala/org/bitcoins/chain/pow/BitcoinPowTest.scala index ebc817855d..13c828b0d3 100644 --- a/chain-test/src/test/scala/org/bitcoins/chain/pow/BitcoinPowTest.scala +++ b/chain-test/src/test/scala/org/bitcoins/chain/pow/BitcoinPowTest.scala @@ -1,6 +1,7 @@ package org.bitcoins.chain.pow import akka.actor.ActorSystem +import org.bitcoins.chain.blockchain.Blockchain import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.models.BlockHeaderDAO import org.bitcoins.core.protocol.blockchain.MainNetChainParams @@ -32,14 +33,15 @@ class BitcoinPowTest extends ChainUnitTest { it must "NOT calculate a POW change when one is not needed" inFixtured { case ChainFixture.Empty => - val blockHeaderDAO = BlockHeaderDAO() - val header1 = ChainTestUtil.ValidPOWChange.blockHeaderDb566494 + val blockchain = Blockchain.fromHeaders( + Vector(ChainTestUtil.ValidPOWChange.blockHeaderDb566494)) val header2 = ChainTestUtil.ValidPOWChange.blockHeaderDb566495 - val nextWorkF = - Pow.getNetworkWorkRequired(header1, header2.blockHeader, blockHeaderDAO) + val nextWork = + Pow.getNetworkWorkRequired(newPotentialTip = header2.blockHeader, + blockchain = blockchain) - nextWorkF.map(nextWork => assert(nextWork == header1.nBits)) + assert(nextWork == blockchain.tip.nBits) } it must "calculate a pow change as per the bitcoin network" inFixtured { @@ -48,13 +50,12 @@ class BitcoinPowTest extends ChainUnitTest { val currentTipDb = ChainTestUtil.ValidPOWChange.blockHeaderDb566495 val expectedNextWork = ChainTestUtil.ValidPOWChange.blockHeader566496.nBits - val calculatedWorkF = + val calculatedWork = Pow.calculateNextWorkRequired(currentTipDb, firstBlockDb, MainNetChainParams) - calculatedWorkF.map(calculatedWork => - assert(calculatedWork == expectedNextWork)) + assert(calculatedWork == expectedNextWork) } it must "GetNextWorkRequired correctly" taggedAs ChainFixtureTag.PopulatedBlockHeaderDAO inFixtured { @@ -66,14 +67,15 @@ class BitcoinPowTest extends ChainUnitTest { (ChainUnitTest.FIRST_POW_CHANGE + 1 until ChainUnitTest.FIRST_POW_CHANGE + 1 + iterations) .map { height => val blockF = blockHeaderDAO.getAtHeight(height).map(_.head) + val blockchainF = + blockF.flatMap(b => blockHeaderDAO.getBlockchainFrom(b)) val nextBlockF = blockHeaderDAO.getAtHeight(height + 1).map(_.head) for { - currentTip <- blockF + blockchain <- blockchainF nextTip <- nextBlockF - nextNBits <- Pow.getNetworkWorkRequired(currentTip, - nextTip.blockHeader, - blockHeaderDAO) + nextNBits = Pow.getNetworkWorkRequired(nextTip.blockHeader, + blockchain) } yield assert(nextNBits == nextTip.nBits) } diff --git a/chain-test/src/test/scala/org/bitcoins/chain/validation/TipValidationTest.scala b/chain-test/src/test/scala/org/bitcoins/chain/validation/TipValidationTest.scala index 34ad7d4f65..27f006a002 100644 --- a/chain-test/src/test/scala/org/bitcoins/chain/validation/TipValidationTest.scala +++ b/chain-test/src/test/scala/org/bitcoins/chain/validation/TipValidationTest.scala @@ -18,6 +18,7 @@ import org.scalatest.{Assertion, FutureOutcome} import scala.concurrent.Future import org.bitcoins.chain.config.ChainAppConfig import com.typesafe.config.ConfigFactory +import org.bitcoins.chain.blockchain.Blockchain import org.bitcoins.server.BitcoinSAppConfig class TipValidationTest extends ChainUnitTest { @@ -37,13 +38,14 @@ class TipValidationTest extends ChainUnitTest { //blocks 566,092 and 566,093 val newValidTip = BlockHeaderHelper.header1 val currentTipDb = BlockHeaderHelper.header2Db + val blockchain = Blockchain.fromHeaders(Vector(currentTipDb)) it must "connect two blocks with that are valid" in { bhDAO => val newValidTipDb = BlockHeaderDbHelper.fromBlockHeader(566093, newValidTip) val expected = TipUpdateResult.Success(newValidTipDb) - runTest(newValidTip, expected, bhDAO) + runTest(newValidTip, expected, blockchain) } it must "fail to connect two blocks that do not reference prev block hash correctly" in { @@ -52,30 +54,27 @@ class TipValidationTest extends ChainUnitTest { val expected = TipUpdateResult.BadPreviousBlockHash(badPrevHash) - runTest(badPrevHash, expected, bhDAO) + runTest(badPrevHash, expected, blockchain) } it must "fail to connect two blocks with two different POW requirements at the wrong interval" in { bhDAO => val badPOW = BlockHeaderHelper.badNBits val expected = TipUpdateResult.BadPOW(badPOW) - runTest(badPOW, expected, bhDAO) + runTest(badPOW, expected, blockchain) } it must "fail to connect two blocks with a bad nonce" in { bhDAO => val badNonce = BlockHeaderHelper.badNonce val expected = TipUpdateResult.BadNonce(badNonce) - runTest(badNonce, expected, bhDAO) + runTest(badNonce, expected, blockchain) } private def runTest( header: BlockHeader, expected: TipUpdateResult, - blockHeaderDAO: BlockHeaderDAO, - currentTipDbDefault: BlockHeaderDb = currentTipDb): Future[Assertion] = { - val checkTipF = - TipValidation.checkNewTip(header, currentTipDbDefault, blockHeaderDAO) - - checkTipF.map(validationResult => assert(validationResult == expected)) + blockchain: Blockchain): Assertion = { + val result = TipValidation.checkNewTip(header, blockchain) + assert(result == expected) } } diff --git a/chain/src/main/scala/org/bitcoins/chain/blockchain/Blockchain.scala b/chain/src/main/scala/org/bitcoins/chain/blockchain/Blockchain.scala index 8d0b88eb23..669911bf51 100644 --- a/chain/src/main/scala/org/bitcoins/chain/blockchain/Blockchain.scala +++ b/chain/src/main/scala/org/bitcoins/chain/blockchain/Blockchain.scala @@ -1,13 +1,12 @@ package org.bitcoins.chain.blockchain -import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb} +import org.bitcoins.chain.config.ChainAppConfig +import org.bitcoins.chain.models.BlockHeaderDb import org.bitcoins.chain.validation.{TipUpdateResult, TipValidation} import org.bitcoins.core.protocol.blockchain.BlockHeader +import org.bitcoins.db.ChainVerificationLogger import scala.collection.{IndexedSeqLike, mutable} -import scala.concurrent.{ExecutionContext, Future} -import org.bitcoins.chain.config.ChainAppConfig -import org.bitcoins.db.ChainVerificationLogger /** * In memory implementation of a blockchain @@ -37,7 +36,22 @@ case class Blockchain(headers: Vector[BlockHeaderDb]) override def length: Int = headers.length /** @inheritdoc */ - override def apply(idx: Int): BlockHeaderDb = headers(idx) + override def apply(idx: Int): BlockHeaderDb = headers.apply(idx) + + /** Finds a block header at a given height */ + def findAtHeight(height: Int): Option[BlockHeaderDb] = + find(_.height == height) + + /** Splits the blockchain at the header, returning a new blockchain where the best tip is the given header */ + def fromHeader(header: BlockHeaderDb): Option[Blockchain] = { + val headerIdxOpt = headers.zipWithIndex.find(_._1 == header) + headerIdxOpt.map { + case (header, idx) => + val newChain = Blockchain.fromHeaders(headers.splitAt(idx)._2) + require(newChain.tip == header) + newChain + } + } } @@ -55,100 +69,87 @@ object Blockchain extends ChainVerificationLogger { * We then attempt to connect this block header to all of our current * chain tips. * @param header the block header to connect to our chain - * @param blockHeaderDAO where we can find our blockchain - * @param ec + * @param blockchains the blockchain we are attempting to connect to * @return a [[scala.concurrent.Future Future]] that contains a [[org.bitcoins.chain.blockchain.BlockchainUpdate BlockchainUpdate]] indicating * we [[org.bitcoins.chain.blockchain.BlockchainUpdate.Successful successful]] connected the tip, * or [[org.bitcoins.chain.blockchain.BlockchainUpdate.Failed Failed]] to connect to a tip */ - def connectTip( - header: BlockHeader, - blockHeaderDAO: BlockHeaderDAO, - blockchains: Vector[Blockchain])( - implicit ec: ExecutionContext, - conf: ChainAppConfig): Future[BlockchainUpdate] = { + def connectTip(header: BlockHeader, blockchains: Vector[Blockchain])( + implicit conf: ChainAppConfig): BlockchainUpdate = { logger.debug( s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain") - val tipResultF: Future[BlockchainUpdate] = { - val nested: Vector[Future[BlockchainUpdate]] = blockchains.map { - blockchain => - val prevBlockHeaderIdxOpt = - blockchain.headers.zipWithIndex.find { - case (headerDb, _) => - headerDb.hashBE == header.previousBlockHashBE - } - prevBlockHeaderIdxOpt match { - case None => - logger.warn( - s"No common ancestor found in the chain to connect to ${header.hashBE}") - val err = TipUpdateResult.BadPreviousBlockHash(header) - val failed = BlockchainUpdate.Failed(blockchain = blockchain, - failedHeader = header, - tipUpdateFailure = err) - Future.successful(failed) - - case Some((prevBlockHeader, prevHeaderIdx)) => - //found a header to connect to! - logger.debug( - s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain") - val tipResultF = - TipValidation.checkNewTip(newPotentialTip = header, - currentTip = prevBlockHeader, - blockHeaderDAO = blockHeaderDAO) - - tipResultF.map { tipResult => - tipResult match { - case TipUpdateResult.Success(headerDb) => - logger.debug( - s"Successfully verified=${headerDb.hashBE.hex}, connecting to chain") - val oldChain = - blockchain.takeRight(blockchain.length - prevHeaderIdx) - val newChain = - Blockchain.fromHeaders(headerDb +: oldChain) - BlockchainUpdate.Successful(newChain, headerDb) - case fail: TipUpdateResult.Failure => - logger.warn( - s"Could not verify header=${header.hashBE.hex}, reason=$fail") - BlockchainUpdate.Failed(blockchain, header, fail) - } - } + val tipResult: BlockchainUpdate = { + val nested: Vector[BlockchainUpdate] = blockchains.map { blockchain => + val prevBlockHeaderIdxOpt = + blockchain.headers.zipWithIndex.find { + case (headerDb, _) => + headerDb.hashBE == header.previousBlockHashBE } + prevBlockHeaderIdxOpt match { + case None => + logger.warn( + s"No common ancestor found in the chain to connect to ${header.hashBE}") + val err = TipUpdateResult.BadPreviousBlockHash(header) + val failed = BlockchainUpdate.Failed(blockchain = blockchain, + failedHeader = header, + tipUpdateFailure = err) + failed + + case Some((prevBlockHeader, prevHeaderIdx)) => + //found a header to connect to! + logger.debug( + s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain") + val chain = blockchain.fromHeader(prevBlockHeader) + val tipResult = + TipValidation.checkNewTip(newPotentialTip = header, chain.get) + + tipResult match { + case TipUpdateResult.Success(headerDb) => + logger.debug( + s"Successfully verified=${headerDb.hashBE.hex}, connecting to chain") + val oldChain = + blockchain.takeRight(blockchain.length - prevHeaderIdx) + val newChain = + Blockchain.fromHeaders(headerDb +: oldChain) + BlockchainUpdate.Successful(newChain, headerDb) + case fail: TipUpdateResult.Failure => + logger.warn( + s"Could not verify header=${header.hashBE.hex}, reason=$fail") + BlockchainUpdate.Failed(blockchain, header, fail) + } + } } - parseSuccessOrFailure(nested = nested) + parseSuccessOrFailure(nested) } - tipResultF + tipResult } /** Takes in a vector of blockchain updates being executed asynchronously, we can only connect one [[BlockHeader header]] * to a tip successfully, which means _all_ other [[BlockchainUpdate updates]] must fail. This is a helper method * to find the one [[BlockchainUpdate.Successful successful]] update, or else returns one of the [[BlockchainUpdate.Failed failures]] - * @param nested - * @param ec * @return */ - private def parseSuccessOrFailure(nested: Vector[Future[BlockchainUpdate]])( - implicit ec: ExecutionContext): Future[BlockchainUpdate] = { - require(nested.nonEmpty, + private def parseSuccessOrFailure( + updates: Vector[BlockchainUpdate]): BlockchainUpdate = { + require(updates.nonEmpty, s"Cannot parse success or failure if we don't have any updates!") - val successfulTipOptF: Future[Option[BlockchainUpdate]] = { - Future.find(nested) { + val successfulTipOpt: Option[BlockchainUpdate] = { + updates.find { case update: BlockchainUpdate => update.isInstanceOf[BlockchainUpdate.Successful] } } - successfulTipOptF.flatMap { - case Some(update) => Future.successful(update) + successfulTipOpt match { + case Some(update) => update case None => //if we didn't successfully connect a tip, just take the first failure we see - Future - .find(nested) { - case update: BlockchainUpdate => - update.isInstanceOf[BlockchainUpdate.Failed] - } - .map(_.get) + updates.find { + case update: BlockchainUpdate => + update.isInstanceOf[BlockchainUpdate.Failed] + }.get } } } diff --git a/chain/src/main/scala/org/bitcoins/chain/blockchain/ChainHandler.scala b/chain/src/main/scala/org/bitcoins/chain/blockchain/ChainHandler.scala index 72eb0b3e20..a57f85c101 100644 --- a/chain/src/main/scala/org/bitcoins/chain/blockchain/ChainHandler.scala +++ b/chain/src/main/scala/org/bitcoins/chain/blockchain/ChainHandler.scala @@ -3,16 +3,18 @@ package org.bitcoins.chain.blockchain import org.bitcoins.chain.api.ChainApi import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb} +import org.bitcoins.chain.validation.TipUpdateResult +import org.bitcoins.chain.validation.TipUpdateResult.{ + BadNonce, + BadPOW, + BadPreviousBlockHash +} import org.bitcoins.core.crypto.DoubleSha256DigestBE import org.bitcoins.core.protocol.blockchain.BlockHeader +import org.bitcoins.core.util.FutureUtil +import org.bitcoins.db.ChainVerificationLogger import scala.concurrent.{ExecutionContext, Future} -import org.bitcoins.db.ChainVerificationLogger -import org.bitcoins.chain.validation.TipUpdateResult.BadNonce -import org.bitcoins.chain.validation.TipUpdateResult.BadPOW -import org.bitcoins.chain.validation.TipUpdateResult.BadPreviousBlockHash -import org.bitcoins.core.util.FutureUtil -import org.bitcoins.chain.validation.TipUpdateResult /** * Chain Handler is meant to be the reference implementation @@ -51,12 +53,10 @@ case class ChainHandler( logger.debug( s"Processing header=${header.hashBE.hex}, previousHash=${header.previousBlockHashBE.hex}") - val blockchainUpdateF = Blockchain.connectTip(header = header, - blockHeaderDAO = - blockHeaderDAO, - blockchains = blockchains) + val blockchainUpdate = + Blockchain.connectTip(header = header, blockchains = blockchains) - val newHandlerF = blockchainUpdateF.flatMap { + val newHandlerF = blockchainUpdate match { case BlockchainUpdate.Successful(newChain, updatedHeader) => //now we have successfully connected the header, we need to insert //it into the database @@ -96,12 +96,6 @@ case class ChainHandler( } } - blockchainUpdateF.failed.foreach { err => - logger.error( - s"Failed to connect header=${header.hashBE.hex} err=${err.getMessage}") - - } - newHandlerF } diff --git a/chain/src/main/scala/org/bitcoins/chain/models/BlockHeaderDAO.scala b/chain/src/main/scala/org/bitcoins/chain/models/BlockHeaderDAO.scala index c700c367c5..8d2dd3d999 100644 --- a/chain/src/main/scala/org/bitcoins/chain/models/BlockHeaderDAO.scala +++ b/chain/src/main/scala/org/bitcoins/chain/models/BlockHeaderDAO.scala @@ -184,17 +184,23 @@ case class BlockHeaderDAO()( def getBlockchains()( implicit ec: ExecutionContext): Future[Vector[Blockchain]] = { val chainTipsF = chainTips - val diffInterval = appConfig.chain.difficultyChangeInterval chainTipsF.flatMap { tips => val nestedFuture: Vector[Future[Blockchain]] = tips.map { tip => - val height = Math.max(0, tip.height - diffInterval) - val headersF = getBetweenHeights(from = height, to = tip.height) - headersF.map(headers => Blockchain.fromHeaders(headers.reverse)) + getBlockchainFrom(tip) } Future.sequence(nestedFuture) } } + /** Retrieves a blockchain with the best tip being the given header */ + def getBlockchainFrom(header: BlockHeaderDb)( + implicit ec: ExecutionContext): Future[Blockchain] = { + val diffInterval = appConfig.chain.difficultyChangeInterval + val height = Math.max(0, header.height - diffInterval) + val headersF = getBetweenHeights(from = height, to = header.height) + headersF.map(headers => Blockchain.fromHeaders(headers.reverse)) + } + /** Finds a [[org.bitcoins.chain.models.BlockHeaderDb block header]] that satisfies the given predicate, else returns None */ def find(f: BlockHeaderDb => Boolean)( implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]] = { diff --git a/chain/src/main/scala/org/bitcoins/chain/pow/Pow.scala b/chain/src/main/scala/org/bitcoins/chain/pow/Pow.scala index 09b2d74ff1..7461aa09a5 100644 --- a/chain/src/main/scala/org/bitcoins/chain/pow/Pow.scala +++ b/chain/src/main/scala/org/bitcoins/chain/pow/Pow.scala @@ -1,13 +1,12 @@ package org.bitcoins.chain.pow -import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb} +import org.bitcoins.chain.blockchain.Blockchain +import org.bitcoins.chain.models.{BlockHeaderDb} import org.bitcoins.core.number.UInt32 import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.core.protocol.blockchain.{BlockHeader, ChainParams} import org.bitcoins.core.util.NumberUtil -import scala.concurrent.{ExecutionContext, Future} - /** * Implements functions found inside of bitcoin core's * @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp pow.cpp]] @@ -17,68 +16,63 @@ sealed abstract class Pow { /** * Gets the next proof of work requirement for a block * @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp#L13 Mimics bitcoin core implmentation]] - * @param tip - * @param newPotentialTip - * @return */ def getNetworkWorkRequired( - tip: BlockHeaderDb, newPotentialTip: BlockHeader, - blockHeaderDAO: BlockHeaderDAO)( - implicit ec: ExecutionContext, - config: ChainAppConfig): Future[UInt32] = { + blockchain: Blockchain)(implicit config: ChainAppConfig): UInt32 = { val chainParams = config.chain + val tip = blockchain.tip val currentHeight = tip.height - val powLimit = NumberUtil.targetCompression(bigInteger = - chainParams.powLimit, - isNegative = false) - if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) { - if (chainParams.allowMinDifficultyBlocks) { - // Special difficulty rule for testnet: - // If the new block's timestamp is more than 2* 10 minutes - // then allow mining of a min-difficulty block. - if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) { - Future.successful(powLimit) - } else { - // Return the last non-special-min-difficulty-rules-block - //while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit) - // pindex = pindex->pprev; - val nonMinDiffF = blockHeaderDAO.find { h => - h.nBits != powLimit || h.height % chainParams.difficultyChangeInterval == 0 - } + val powLimit: UInt32 = + if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) { + if (chainParams.allowMinDifficultyBlocks) { + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 2* 10 minutes + // then allow mining of a min-difficulty block. + if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) { + chainParams.compressedPowLimit + } else { + // Return the last non-special-min-difficulty-rules-block + //while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit) + // pindex = pindex->pprev; + val nonMinDiffF = blockchain.find { h => + h.nBits != chainParams.compressedPowLimit || h.height % chainParams.difficultyChangeInterval == 0 + } - nonMinDiffF.map { - case Some(bh) => bh.nBits - case None => - //if we can't find a non min diffulty block, let's just fail - throw new RuntimeException( - s"Could not find non mindiffulty block in chain! hash=${tip.hashBE.hex} height=${currentHeight}") + nonMinDiffF match { + case Some(bh) => bh.nBits + case None => + //if we can't find a non min diffulty block, let's just fail + throw new RuntimeException( + s"Could not find non mindiffulty block in chain! hash=${tip.hashBE.hex} height=${currentHeight}") + } } + } else { + tip.blockHeader.nBits } } else { - Future.successful(tip.blockHeader.nBits) - } - } else { - val firstHeight = currentHeight - (chainParams.difficultyChangeInterval - 1) + val firstHeight: Int = currentHeight - (chainParams.difficultyChangeInterval - 1) - require(firstHeight >= 0, - s"We must have our first height be postive, got=${firstHeight}") + require(firstHeight >= 0, + s"We must have our first height be postive, got=${firstHeight}") + + val firstBlockAtIntervalOpt: Option[BlockHeaderDb] = + blockchain.findAtHeight(firstHeight) + + firstBlockAtIntervalOpt match { + case Some(firstBlockAtInterval) => + calculateNextWorkRequired(currentTip = tip, + firstBlockAtInterval, + chainParams) + case None => + throw new RuntimeException( + s"Could not find block at height=${firstHeight} out of ${blockchain.length} headers to calculate pow difficutly change") + } - val firstBlockAtIntervalF: Future[Option[BlockHeaderDb]] = { - blockHeaderDAO.getAncestorAtHeight(tip, firstHeight) } - firstBlockAtIntervalF.flatMap { - case Some(firstBlock) => - calculateNextWorkRequired(currentTip = tip, firstBlock, chainParams) - case None => - Future.failed( - new IllegalArgumentException( - s"Could not find ancestor for block=${tip.hashBE.hex}")) - } - - } + powLimit } /** @@ -92,9 +86,9 @@ sealed abstract class Pow { def calculateNextWorkRequired( currentTip: BlockHeaderDb, firstBlock: BlockHeaderDb, - chainParams: ChainParams): Future[UInt32] = { + chainParams: ChainParams): UInt32 = { if (chainParams.noRetargeting) { - Future.successful(currentTip.nBits) + currentTip.nBits } else { var actualTimespan = (currentTip.time - firstBlock.time).toLong val timespanSeconds = chainParams.powTargetTimeSpan.toSeconds @@ -120,7 +114,7 @@ sealed abstract class Pow { val newTarget = NumberUtil.targetCompression(bnNew, false) - Future.successful(newTarget) + newTarget } } } diff --git a/chain/src/main/scala/org/bitcoins/chain/validation/TipValidation.scala b/chain/src/main/scala/org/bitcoins/chain/validation/TipValidation.scala index 7b636f8f37..bf364ffce7 100644 --- a/chain/src/main/scala/org/bitcoins/chain/validation/TipValidation.scala +++ b/chain/src/main/scala/org/bitcoins/chain/validation/TipValidation.scala @@ -1,16 +1,12 @@ package org.bitcoins.chain.validation -import org.bitcoins.chain.models.{ - BlockHeaderDAO, - BlockHeaderDb, - BlockHeaderDbHelper -} +import org.bitcoins.chain.blockchain.Blockchain +import org.bitcoins.chain.models.{BlockHeaderDb, BlockHeaderDbHelper} import org.bitcoins.chain.pow.Pow import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.util.NumberUtil -import scala.concurrent.{ExecutionContext, Future} import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.db.ChainVerificationLogger @@ -28,52 +24,44 @@ sealed abstract class TipValidation extends ChainVerificationLogger { * assigned to a [[org.bitcoins.core.protocol.blockchain.BlockHeader BlockHeader]] after all these * validation checks occur * */ - def checkNewTip( - newPotentialTip: BlockHeader, - currentTip: BlockHeaderDb, - blockHeaderDAO: BlockHeaderDAO)( - implicit ec: ExecutionContext, - conf: ChainAppConfig): Future[TipUpdateResult] = { + def checkNewTip(newPotentialTip: BlockHeader, blockchain: Blockchain)( + implicit conf: ChainAppConfig): TipUpdateResult = { val header = newPotentialTip + val currentTip = blockchain.tip logger.trace( s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}") - val powCheckF = isBadPow(newPotentialTip = newPotentialTip, - currentTip = currentTip, - blockHeaderDAO = blockHeaderDAO) + val expectedWork: UInt32 = + isBadPow(newPotentialTip = newPotentialTip, blockchain = blockchain)(conf) - val connectTipResultF: Future[TipUpdateResult] = { - powCheckF.map { expectedWork => - if (header.previousBlockHashBE != currentTip.hashBE) { - logger.warn( - s"Failed to connect tip=${header.hashBE.hex} to current chain") - TipUpdateResult.BadPreviousBlockHash(newPotentialTip) - } else if (header.nBits != expectedWork) { - //https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L19 - TipUpdateResult.BadPOW(newPotentialTip) - } else if (isBadNonce(newPotentialTip)) { - TipUpdateResult.BadNonce(newPotentialTip) - } else { - val headerDb = BlockHeaderDbHelper.fromBlockHeader( - height = currentTip.height + 1, - bh = newPotentialTip - ) - TipUpdateResult.Success(headerDb) - } + val connectTipResult: TipUpdateResult = { + if (header.previousBlockHashBE != currentTip.hashBE) { + logger.warn( + s"Failed to connect tip=${header.hashBE.hex} to current chain") + TipUpdateResult.BadPreviousBlockHash(newPotentialTip) + } else if (header.nBits != expectedWork) { + //https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L19 + TipUpdateResult.BadPOW(newPotentialTip) + } else if (isBadNonce(newPotentialTip)) { + TipUpdateResult.BadNonce(newPotentialTip) + } else { + val headerDb = BlockHeaderDbHelper.fromBlockHeader( + height = currentTip.height + 1, + bh = newPotentialTip + ) + TipUpdateResult.Success(headerDb) } } - logTipResult(connectTipResultF, currentTip) - connectTipResultF + logTipResult(connectTipResult, currentTip) + connectTipResult } /** Logs the result of [[org.bitcoins.chain.validation.TipValidation.checkNewTip() checkNewTip]] */ private def logTipResult( - connectTipResultF: Future[TipUpdateResult], - currentTip: BlockHeaderDb)( - implicit ec: ExecutionContext, - conf: ChainAppConfig): Unit = { - connectTipResultF.map { + connectTipResult: TipUpdateResult, + currentTip: BlockHeaderDb)(implicit conf: ChainAppConfig): Unit = { + connectTipResult match { case TipUpdateResult.Success(tipDb) => logger.trace( s"Successfully connected ${tipDb.hashBE.hex} with height=${tipDb.height} to block=${currentTip.hashBE.hex} with height=${currentTip.height}") @@ -83,7 +71,6 @@ sealed abstract class TipValidation extends ChainVerificationLogger { s"Failed to connect ${bad.header.hashBE.hex} to ${currentTip.hashBE.hex} with height=${currentTip.height}, reason=${bad}") } - () } @@ -103,15 +90,10 @@ sealed abstract class TipValidation extends ChainVerificationLogger { } } - private def isBadPow( - newPotentialTip: BlockHeader, - currentTip: BlockHeaderDb, - blockHeaderDAO: BlockHeaderDAO)( - implicit ec: ExecutionContext, - config: ChainAppConfig): Future[UInt32] = { - Pow.getNetworkWorkRequired(tip = currentTip, - newPotentialTip = newPotentialTip, - blockHeaderDAO = blockHeaderDAO) + private def isBadPow(newPotentialTip: BlockHeader, blockchain: Blockchain)( + config: ChainAppConfig): UInt32 = { + Pow.getNetworkWorkRequired(newPotentialTip = newPotentialTip, + blockchain = blockchain)(config) } } diff --git a/core/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala b/core/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala index 89b2aedb89..315f7feb11 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala @@ -2,7 +2,6 @@ package org.bitcoins.core.protocol.blockchain import java.math.BigInteger import java.nio.charset.StandardCharsets - import org.bitcoins.core.config.{ BitcoinNetwork, MainNet, @@ -18,7 +17,7 @@ import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptSignature} import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant} import org.bitcoins.core.script.crypto.OP_CHECKSIG -import org.bitcoins.core.util.BitcoinScriptUtil +import org.bitcoins.core.util.{BitcoinScriptUtil, NumberUtil} import scodec.bits.{ByteVector, _} import scala.concurrent.duration.{Duration, DurationInt} @@ -161,6 +160,12 @@ sealed abstract class ChainParams { */ def powLimit: BigInteger + /** The minimum proof of required for a block as specified by [[org.bitcoins.core.protocol.blockchain.ChainParams.powLimit powLimit]], compressed to a UInt32 */ + lazy val compressedPowLimit: UInt32 = { + NumberUtil.targetCompression(bigInteger = powLimit, isNegative = false) + + } + /** The targetted timespan between difficulty adjustments * As of this implementation, all of these are the same in bitcoin core * diff --git a/db-commons/src/main/resources/reference.conf b/db-commons/src/main/resources/reference.conf index 528ed483fc..6adea56e7c 100644 --- a/db-commons/src/main/resources/reference.conf +++ b/db-commons/src/main/resources/reference.conf @@ -3,7 +3,7 @@ bitcoin-s { network = regtest # regtest, testnet3, mainnet logging { - level = info # trace, debug, info, warn, error, off + level = INFO # trace, debug, info, warn, error, off # You can also tune specific module loggers. # They each take the same levels as above.