2019 08 09 Don't use BlockHeaderDAO in TipValidation (#688)

* WIP: De-futurify TipValidatoin.chewNewTip()

* De-futrify POW and TipValidation, now tip connection is done synchronously thanks to our in memory blockchain implementation

* Fix improt issues, unused parameters
This commit is contained in:
Chris Stewart 2019-08-13 10:33:19 -05:00 committed by GitHub
parent 0abc9a77cf
commit 7caf0c355b
10 changed files with 209 additions and 227 deletions

View file

@ -25,11 +25,10 @@ class BlockchainTest extends ChainUnitTest {
val newHeader = val newHeader =
BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb) BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb)
val connectTipF = Blockchain.connectTip(header = newHeader.blockHeader, val connectTip = Blockchain.connectTip(header = newHeader.blockHeader,
blockHeaderDAO = bhDAO, Vector(blockchain))
Vector(blockchain))
connectTipF.map { connectTip match {
case BlockchainUpdate.Successful(_, connectedHeader) => case BlockchainUpdate.Successful(_, connectedHeader) =>
assert(newHeader == connectedHeader) assert(newHeader == connectedHeader)

View file

@ -1,6 +1,7 @@
package org.bitcoins.chain.pow package org.bitcoins.chain.pow
import akka.actor.ActorSystem import akka.actor.ActorSystem
import org.bitcoins.chain.blockchain.Blockchain
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.core.protocol.blockchain.MainNetChainParams 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 { it must "NOT calculate a POW change when one is not needed" inFixtured {
case ChainFixture.Empty => case ChainFixture.Empty =>
val blockHeaderDAO = BlockHeaderDAO() val blockchain = Blockchain.fromHeaders(
val header1 = ChainTestUtil.ValidPOWChange.blockHeaderDb566494 Vector(ChainTestUtil.ValidPOWChange.blockHeaderDb566494))
val header2 = ChainTestUtil.ValidPOWChange.blockHeaderDb566495 val header2 = ChainTestUtil.ValidPOWChange.blockHeaderDb566495
val nextWorkF = val nextWork =
Pow.getNetworkWorkRequired(header1, header2.blockHeader, blockHeaderDAO) 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 { 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 currentTipDb = ChainTestUtil.ValidPOWChange.blockHeaderDb566495
val expectedNextWork = val expectedNextWork =
ChainTestUtil.ValidPOWChange.blockHeader566496.nBits ChainTestUtil.ValidPOWChange.blockHeader566496.nBits
val calculatedWorkF = val calculatedWork =
Pow.calculateNextWorkRequired(currentTipDb, Pow.calculateNextWorkRequired(currentTipDb,
firstBlockDb, firstBlockDb,
MainNetChainParams) MainNetChainParams)
calculatedWorkF.map(calculatedWork => assert(calculatedWork == expectedNextWork)
assert(calculatedWork == expectedNextWork))
} }
it must "GetNextWorkRequired correctly" taggedAs ChainFixtureTag.PopulatedBlockHeaderDAO inFixtured { 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) (ChainUnitTest.FIRST_POW_CHANGE + 1 until ChainUnitTest.FIRST_POW_CHANGE + 1 + iterations)
.map { height => .map { height =>
val blockF = blockHeaderDAO.getAtHeight(height).map(_.head) val blockF = blockHeaderDAO.getAtHeight(height).map(_.head)
val blockchainF =
blockF.flatMap(b => blockHeaderDAO.getBlockchainFrom(b))
val nextBlockF = blockHeaderDAO.getAtHeight(height + 1).map(_.head) val nextBlockF = blockHeaderDAO.getAtHeight(height + 1).map(_.head)
for { for {
currentTip <- blockF blockchain <- blockchainF
nextTip <- nextBlockF nextTip <- nextBlockF
nextNBits <- Pow.getNetworkWorkRequired(currentTip, nextNBits = Pow.getNetworkWorkRequired(nextTip.blockHeader,
nextTip.blockHeader, blockchain)
blockHeaderDAO)
} yield assert(nextNBits == nextTip.nBits) } yield assert(nextNBits == nextTip.nBits)
} }

View file

@ -18,6 +18,7 @@ import org.scalatest.{Assertion, FutureOutcome}
import scala.concurrent.Future import scala.concurrent.Future
import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.config.ChainAppConfig
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import org.bitcoins.chain.blockchain.Blockchain
import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.server.BitcoinSAppConfig
class TipValidationTest extends ChainUnitTest { class TipValidationTest extends ChainUnitTest {
@ -37,13 +38,14 @@ class TipValidationTest extends ChainUnitTest {
//blocks 566,092 and 566,093 //blocks 566,092 and 566,093
val newValidTip = BlockHeaderHelper.header1 val newValidTip = BlockHeaderHelper.header1
val currentTipDb = BlockHeaderHelper.header2Db val currentTipDb = BlockHeaderHelper.header2Db
val blockchain = Blockchain.fromHeaders(Vector(currentTipDb))
it must "connect two blocks with that are valid" in { bhDAO => it must "connect two blocks with that are valid" in { bhDAO =>
val newValidTipDb = val newValidTipDb =
BlockHeaderDbHelper.fromBlockHeader(566093, newValidTip) BlockHeaderDbHelper.fromBlockHeader(566093, newValidTip)
val expected = TipUpdateResult.Success(newValidTipDb) 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 { 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) 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 { it must "fail to connect two blocks with two different POW requirements at the wrong interval" in {
bhDAO => bhDAO =>
val badPOW = BlockHeaderHelper.badNBits val badPOW = BlockHeaderHelper.badNBits
val expected = TipUpdateResult.BadPOW(badPOW) 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 => it must "fail to connect two blocks with a bad nonce" in { bhDAO =>
val badNonce = BlockHeaderHelper.badNonce val badNonce = BlockHeaderHelper.badNonce
val expected = TipUpdateResult.BadNonce(badNonce) val expected = TipUpdateResult.BadNonce(badNonce)
runTest(badNonce, expected, bhDAO) runTest(badNonce, expected, blockchain)
} }
private def runTest( private def runTest(
header: BlockHeader, header: BlockHeader,
expected: TipUpdateResult, expected: TipUpdateResult,
blockHeaderDAO: BlockHeaderDAO, blockchain: Blockchain): Assertion = {
currentTipDbDefault: BlockHeaderDb = currentTipDb): Future[Assertion] = { val result = TipValidation.checkNewTip(header, blockchain)
val checkTipF = assert(result == expected)
TipValidation.checkNewTip(header, currentTipDbDefault, blockHeaderDAO)
checkTipF.map(validationResult => assert(validationResult == expected))
} }
} }

View file

@ -1,13 +1,12 @@
package org.bitcoins.chain.blockchain 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.chain.validation.{TipUpdateResult, TipValidation}
import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.db.ChainVerificationLogger
import scala.collection.{IndexedSeqLike, mutable} 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 * In memory implementation of a blockchain
@ -37,7 +36,22 @@ case class Blockchain(headers: Vector[BlockHeaderDb])
override def length: Int = headers.length override def length: Int = headers.length
/** @inheritdoc */ /** @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 * We then attempt to connect this block header to all of our current
* chain tips. * chain tips.
* @param header the block header to connect to our chain * @param header the block header to connect to our chain
* @param blockHeaderDAO where we can find our blockchain * @param blockchains the blockchain we are attempting to connect to
* @param ec
* @return a [[scala.concurrent.Future Future]] that contains a [[org.bitcoins.chain.blockchain.BlockchainUpdate BlockchainUpdate]] indicating * @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, * 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( def connectTip(header: BlockHeader, blockchains: Vector[Blockchain])(
header: BlockHeader, implicit conf: ChainAppConfig): BlockchainUpdate = {
blockHeaderDAO: BlockHeaderDAO,
blockchains: Vector[Blockchain])(
implicit ec: ExecutionContext,
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")
val tipResultF: Future[BlockchainUpdate] = { val tipResult: BlockchainUpdate = {
val nested: Vector[Future[BlockchainUpdate]] = blockchains.map { val nested: Vector[BlockchainUpdate] = blockchains.map { blockchain =>
blockchain => val prevBlockHeaderIdxOpt =
val prevBlockHeaderIdxOpt = blockchain.headers.zipWithIndex.find {
blockchain.headers.zipWithIndex.find { case (headerDb, _) =>
case (headerDb, _) => headerDb.hashBE == header.previousBlockHashBE
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)
}
}
} }
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]] /** 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 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]] * to find the one [[BlockchainUpdate.Successful successful]] update, or else returns one of the [[BlockchainUpdate.Failed failures]]
* @param nested
* @param ec
* @return * @return
*/ */
private def parseSuccessOrFailure(nested: Vector[Future[BlockchainUpdate]])( private def parseSuccessOrFailure(
implicit ec: ExecutionContext): Future[BlockchainUpdate] = { updates: Vector[BlockchainUpdate]): BlockchainUpdate = {
require(nested.nonEmpty, require(updates.nonEmpty,
s"Cannot parse success or failure if we don't have any updates!") s"Cannot parse success or failure if we don't have any updates!")
val successfulTipOptF: Future[Option[BlockchainUpdate]] = { val successfulTipOpt: Option[BlockchainUpdate] = {
Future.find(nested) { updates.find {
case update: BlockchainUpdate => case update: BlockchainUpdate =>
update.isInstanceOf[BlockchainUpdate.Successful] update.isInstanceOf[BlockchainUpdate.Successful]
} }
} }
successfulTipOptF.flatMap { successfulTipOpt match {
case Some(update) => Future.successful(update) case Some(update) => update
case None => case None =>
//if we didn't successfully connect a tip, just take the first failure we see //if we didn't successfully connect a tip, just take the first failure we see
Future updates.find {
.find(nested) { case update: BlockchainUpdate =>
case update: BlockchainUpdate => update.isInstanceOf[BlockchainUpdate.Failed]
update.isInstanceOf[BlockchainUpdate.Failed] }.get
}
.map(_.get)
} }
} }
} }

View file

@ -3,16 +3,18 @@ package org.bitcoins.chain.blockchain
import org.bitcoins.chain.api.ChainApi import org.bitcoins.chain.api.ChainApi
import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb} 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.crypto.DoubleSha256DigestBE
import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.db.ChainVerificationLogger
import scala.concurrent.{ExecutionContext, Future} 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 * Chain Handler is meant to be the reference implementation
@ -51,12 +53,10 @@ case class ChainHandler(
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 = Blockchain.connectTip(header = header, val blockchainUpdate =
blockHeaderDAO = Blockchain.connectTip(header = header, blockchains = blockchains)
blockHeaderDAO,
blockchains = blockchains)
val newHandlerF = blockchainUpdateF.flatMap { val newHandlerF = blockchainUpdate match {
case BlockchainUpdate.Successful(newChain, 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
@ -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 newHandlerF
} }

View file

@ -184,17 +184,23 @@ case class BlockHeaderDAO()(
def getBlockchains()( def getBlockchains()(
implicit ec: ExecutionContext): Future[Vector[Blockchain]] = { implicit ec: ExecutionContext): Future[Vector[Blockchain]] = {
val chainTipsF = chainTips val chainTipsF = chainTips
val diffInterval = appConfig.chain.difficultyChangeInterval
chainTipsF.flatMap { tips => chainTipsF.flatMap { tips =>
val nestedFuture: Vector[Future[Blockchain]] = tips.map { tip => val nestedFuture: Vector[Future[Blockchain]] = tips.map { tip =>
val height = Math.max(0, tip.height - diffInterval) getBlockchainFrom(tip)
val headersF = getBetweenHeights(from = height, to = tip.height)
headersF.map(headers => Blockchain.fromHeaders(headers.reverse))
} }
Future.sequence(nestedFuture) 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 */ /** Finds a [[org.bitcoins.chain.models.BlockHeaderDb block header]] that satisfies the given predicate, else returns None */
def find(f: BlockHeaderDb => Boolean)( def find(f: BlockHeaderDb => Boolean)(
implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]] = { implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]] = {

View file

@ -1,13 +1,12 @@
package org.bitcoins.chain.pow 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.core.number.UInt32
import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.core.protocol.blockchain.{BlockHeader, ChainParams} import org.bitcoins.core.protocol.blockchain.{BlockHeader, ChainParams}
import org.bitcoins.core.util.NumberUtil import org.bitcoins.core.util.NumberUtil
import scala.concurrent.{ExecutionContext, Future}
/** /**
* Implements functions found inside of bitcoin core's * Implements functions found inside of bitcoin core's
* @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp pow.cpp]] * @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 * 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]] * @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp#L13 Mimics bitcoin core implmentation]]
* @param tip
* @param newPotentialTip
* @return
*/ */
def getNetworkWorkRequired( def getNetworkWorkRequired(
tip: BlockHeaderDb,
newPotentialTip: BlockHeader, newPotentialTip: BlockHeader,
blockHeaderDAO: BlockHeaderDAO)( blockchain: Blockchain)(implicit config: ChainAppConfig): UInt32 = {
implicit ec: ExecutionContext,
config: ChainAppConfig): Future[UInt32] = {
val chainParams = config.chain val chainParams = config.chain
val tip = blockchain.tip
val currentHeight = tip.height val currentHeight = tip.height
val powLimit = NumberUtil.targetCompression(bigInteger = val powLimit: UInt32 =
chainParams.powLimit, if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) {
isNegative = false) if (chainParams.allowMinDifficultyBlocks) {
if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) { // Special difficulty rule for testnet:
if (chainParams.allowMinDifficultyBlocks) { // If the new block's timestamp is more than 2* 10 minutes
// Special difficulty rule for testnet: // then allow mining of a min-difficulty block.
// If the new block's timestamp is more than 2* 10 minutes if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) {
// then allow mining of a min-difficulty block. chainParams.compressedPowLimit
if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) { } else {
Future.successful(powLimit) // Return the last non-special-min-difficulty-rules-block
} else { //while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
// Return the last non-special-min-difficulty-rules-block // pindex = pindex->pprev;
//while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit) val nonMinDiffF = blockchain.find { h =>
// pindex = pindex->pprev; h.nBits != chainParams.compressedPowLimit || h.height % chainParams.difficultyChangeInterval == 0
val nonMinDiffF = blockHeaderDAO.find { h => }
h.nBits != powLimit || h.height % chainParams.difficultyChangeInterval == 0
}
nonMinDiffF.map { nonMinDiffF match {
case Some(bh) => bh.nBits case Some(bh) => bh.nBits
case None => case None =>
//if we can't find a non min diffulty block, let's just fail //if we can't find a non min diffulty block, let's just fail
throw new RuntimeException( throw new RuntimeException(
s"Could not find non mindiffulty block in chain! hash=${tip.hashBE.hex} height=${currentHeight}") s"Could not find non mindiffulty block in chain! hash=${tip.hashBE.hex} height=${currentHeight}")
}
} }
} else {
tip.blockHeader.nBits
} }
} else { } else {
Future.successful(tip.blockHeader.nBits) val firstHeight: Int = currentHeight - (chainParams.difficultyChangeInterval - 1)
}
} else {
val firstHeight = currentHeight - (chainParams.difficultyChangeInterval - 1)
require(firstHeight >= 0, require(firstHeight >= 0,
s"We must have our first height be postive, got=${firstHeight}") 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 { powLimit
case Some(firstBlock) =>
calculateNextWorkRequired(currentTip = tip, firstBlock, chainParams)
case None =>
Future.failed(
new IllegalArgumentException(
s"Could not find ancestor for block=${tip.hashBE.hex}"))
}
}
} }
/** /**
@ -92,9 +86,9 @@ sealed abstract class Pow {
def calculateNextWorkRequired( def calculateNextWorkRequired(
currentTip: BlockHeaderDb, currentTip: BlockHeaderDb,
firstBlock: BlockHeaderDb, firstBlock: BlockHeaderDb,
chainParams: ChainParams): Future[UInt32] = { chainParams: ChainParams): UInt32 = {
if (chainParams.noRetargeting) { if (chainParams.noRetargeting) {
Future.successful(currentTip.nBits) currentTip.nBits
} else { } else {
var actualTimespan = (currentTip.time - firstBlock.time).toLong var actualTimespan = (currentTip.time - firstBlock.time).toLong
val timespanSeconds = chainParams.powTargetTimeSpan.toSeconds val timespanSeconds = chainParams.powTargetTimeSpan.toSeconds
@ -120,7 +114,7 @@ sealed abstract class Pow {
val newTarget = NumberUtil.targetCompression(bnNew, false) val newTarget = NumberUtil.targetCompression(bnNew, false)
Future.successful(newTarget) newTarget
} }
} }
} }

View file

@ -1,16 +1,12 @@
package org.bitcoins.chain.validation package org.bitcoins.chain.validation
import org.bitcoins.chain.models.{ import org.bitcoins.chain.blockchain.Blockchain
BlockHeaderDAO, import org.bitcoins.chain.models.{BlockHeaderDb, BlockHeaderDbHelper}
BlockHeaderDb,
BlockHeaderDbHelper
}
import org.bitcoins.chain.pow.Pow import org.bitcoins.chain.pow.Pow
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.util.NumberUtil import org.bitcoins.core.util.NumberUtil
import scala.concurrent.{ExecutionContext, Future}
import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.db.ChainVerificationLogger 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 * assigned to a [[org.bitcoins.core.protocol.blockchain.BlockHeader BlockHeader]] after all these
* validation checks occur * validation checks occur
* */ * */
def checkNewTip( def checkNewTip(newPotentialTip: BlockHeader, blockchain: Blockchain)(
newPotentialTip: BlockHeader, implicit conf: ChainAppConfig): TipUpdateResult = {
currentTip: BlockHeaderDb,
blockHeaderDAO: BlockHeaderDAO)(
implicit ec: ExecutionContext,
conf: ChainAppConfig): Future[TipUpdateResult] = {
val header = newPotentialTip val header = newPotentialTip
val currentTip = blockchain.tip
logger.trace( logger.trace(
s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}") s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}")
val powCheckF = isBadPow(newPotentialTip = newPotentialTip, val expectedWork: UInt32 =
currentTip = currentTip, isBadPow(newPotentialTip = newPotentialTip, blockchain = blockchain)(conf)
blockHeaderDAO = blockHeaderDAO)
val connectTipResultF: Future[TipUpdateResult] = { val connectTipResult: TipUpdateResult = {
powCheckF.map { expectedWork => if (header.previousBlockHashBE != currentTip.hashBE) {
if (header.previousBlockHashBE != currentTip.hashBE) { logger.warn(
logger.warn( s"Failed to connect tip=${header.hashBE.hex} to current chain")
s"Failed to connect tip=${header.hashBE.hex} to current chain") TipUpdateResult.BadPreviousBlockHash(newPotentialTip)
TipUpdateResult.BadPreviousBlockHash(newPotentialTip) } else if (header.nBits != expectedWork) {
} else if (header.nBits != expectedWork) { //https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L19
//https://github.com/bitcoin/bitcoin/blob/eb7daf4d600eeb631427c018a984a77a34aca66e/src/pow.cpp#L19 TipUpdateResult.BadPOW(newPotentialTip)
TipUpdateResult.BadPOW(newPotentialTip) } else if (isBadNonce(newPotentialTip)) {
} else if (isBadNonce(newPotentialTip)) { TipUpdateResult.BadNonce(newPotentialTip)
TipUpdateResult.BadNonce(newPotentialTip) } else {
} else { val headerDb = BlockHeaderDbHelper.fromBlockHeader(
val headerDb = BlockHeaderDbHelper.fromBlockHeader( height = currentTip.height + 1,
height = currentTip.height + 1, bh = newPotentialTip
bh = newPotentialTip )
) TipUpdateResult.Success(headerDb)
TipUpdateResult.Success(headerDb)
}
} }
} }
logTipResult(connectTipResultF, currentTip) logTipResult(connectTipResult, currentTip)
connectTipResultF connectTipResult
} }
/** Logs the result of [[org.bitcoins.chain.validation.TipValidation.checkNewTip() checkNewTip]] */ /** Logs the result of [[org.bitcoins.chain.validation.TipValidation.checkNewTip() checkNewTip]] */
private def logTipResult( private def logTipResult(
connectTipResultF: Future[TipUpdateResult], connectTipResult: TipUpdateResult,
currentTip: BlockHeaderDb)( currentTip: BlockHeaderDb)(implicit conf: ChainAppConfig): Unit = {
implicit ec: ExecutionContext, connectTipResult match {
conf: ChainAppConfig): Unit = {
connectTipResultF.map {
case TipUpdateResult.Success(tipDb) => case TipUpdateResult.Success(tipDb) =>
logger.trace( logger.trace(
s"Successfully connected ${tipDb.hashBE.hex} with height=${tipDb.height} to block=${currentTip.hashBE.hex} with height=${currentTip.height}") 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}") 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( private def isBadPow(newPotentialTip: BlockHeader, blockchain: Blockchain)(
newPotentialTip: BlockHeader, config: ChainAppConfig): UInt32 = {
currentTip: BlockHeaderDb, Pow.getNetworkWorkRequired(newPotentialTip = newPotentialTip,
blockHeaderDAO: BlockHeaderDAO)( blockchain = blockchain)(config)
implicit ec: ExecutionContext,
config: ChainAppConfig): Future[UInt32] = {
Pow.getNetworkWorkRequired(tip = currentTip,
newPotentialTip = newPotentialTip,
blockHeaderDAO = blockHeaderDAO)
} }
} }

View file

@ -2,7 +2,6 @@ package org.bitcoins.core.protocol.blockchain
import java.math.BigInteger import java.math.BigInteger
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import org.bitcoins.core.config.{ import org.bitcoins.core.config.{
BitcoinNetwork, BitcoinNetwork,
MainNet, MainNet,
@ -18,7 +17,7 @@ import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptSignature}
import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant} import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant}
import org.bitcoins.core.script.crypto.OP_CHECKSIG 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 scodec.bits.{ByteVector, _}
import scala.concurrent.duration.{Duration, DurationInt} import scala.concurrent.duration.{Duration, DurationInt}
@ -161,6 +160,12 @@ sealed abstract class ChainParams {
*/ */
def powLimit: BigInteger 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 /** The targetted timespan between difficulty adjustments
* As of this implementation, all of these are the same in bitcoin core * As of this implementation, all of these are the same in bitcoin core
* *

View file

@ -3,7 +3,7 @@ bitcoin-s {
network = regtest # regtest, testnet3, mainnet network = regtest # regtest, testnet3, mainnet
logging { logging {
level = info # trace, debug, info, warn, error, off level = INFO # trace, debug, info, warn, error, off
# You can also tune specific module loggers. # You can also tune specific module loggers.
# They each take the same levels as above. # They each take the same levels as above.