mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
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:
parent
0abc9a77cf
commit
7caf0c355b
10 changed files with 209 additions and 227 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,24 +69,18 @@ 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, _) =>
|
||||||
|
@ -86,18 +94,16 @@ object Blockchain extends ChainVerificationLogger {
|
||||||
val failed = BlockchainUpdate.Failed(blockchain = blockchain,
|
val failed = BlockchainUpdate.Failed(blockchain = blockchain,
|
||||||
failedHeader = header,
|
failedHeader = header,
|
||||||
tipUpdateFailure = err)
|
tipUpdateFailure = err)
|
||||||
Future.successful(failed)
|
failed
|
||||||
|
|
||||||
case Some((prevBlockHeader, prevHeaderIdx)) =>
|
case Some((prevBlockHeader, prevHeaderIdx)) =>
|
||||||
//found a header to connect to!
|
//found a header to connect to!
|
||||||
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 =
|
val chain = blockchain.fromHeader(prevBlockHeader)
|
||||||
TipValidation.checkNewTip(newPotentialTip = header,
|
val tipResult =
|
||||||
currentTip = prevBlockHeader,
|
TipValidation.checkNewTip(newPotentialTip = header, chain.get)
|
||||||
blockHeaderDAO = blockHeaderDAO)
|
|
||||||
|
|
||||||
tipResultF.map { tipResult =>
|
|
||||||
tipResult match {
|
tipResult match {
|
||||||
case TipUpdateResult.Success(headerDb) =>
|
case TipUpdateResult.Success(headerDb) =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -114,41 +120,36 @@ object Blockchain extends ChainVerificationLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
parseSuccessOrFailure(nested)
|
||||||
parseSuccessOrFailure(nested = 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]] = {
|
||||||
|
|
|
@ -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,38 +16,31 @@ 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,
|
|
||||||
isNegative = false)
|
|
||||||
if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) {
|
if ((currentHeight + 1) % chainParams.difficultyChangeInterval != 0) {
|
||||||
if (chainParams.allowMinDifficultyBlocks) {
|
if (chainParams.allowMinDifficultyBlocks) {
|
||||||
// Special difficulty rule for testnet:
|
// Special difficulty rule for testnet:
|
||||||
// If the new block's timestamp is more than 2* 10 minutes
|
// If the new block's timestamp is more than 2* 10 minutes
|
||||||
// then allow mining of a min-difficulty block.
|
// then allow mining of a min-difficulty block.
|
||||||
if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) {
|
if (newPotentialTip.time.toLong > tip.blockHeader.time.toLong + chainParams.powTargetSpacing.toSeconds * 2) {
|
||||||
Future.successful(powLimit)
|
chainParams.compressedPowLimit
|
||||||
} else {
|
} else {
|
||||||
// Return the last non-special-min-difficulty-rules-block
|
// Return the last non-special-min-difficulty-rules-block
|
||||||
//while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
|
//while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
|
||||||
// pindex = pindex->pprev;
|
// pindex = pindex->pprev;
|
||||||
val nonMinDiffF = blockHeaderDAO.find { h =>
|
val nonMinDiffF = blockchain.find { h =>
|
||||||
h.nBits != powLimit || h.height % chainParams.difficultyChangeInterval == 0
|
h.nBits != chainParams.compressedPowLimit || 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
|
||||||
|
@ -57,28 +49,30 @@ sealed abstract class Pow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Future.successful(tip.blockHeader.nBits)
|
tip.blockHeader.nBits
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val firstHeight = currentHeight - (chainParams.difficultyChangeInterval - 1)
|
val firstHeight: Int = 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 firstBlockAtIntervalF: Future[Option[BlockHeaderDb]] = {
|
val firstBlockAtIntervalOpt: Option[BlockHeaderDb] =
|
||||||
blockHeaderDAO.getAncestorAtHeight(tip, firstHeight)
|
blockchain.findAtHeight(firstHeight)
|
||||||
}
|
|
||||||
|
|
||||||
firstBlockAtIntervalF.flatMap {
|
firstBlockAtIntervalOpt match {
|
||||||
case Some(firstBlock) =>
|
case Some(firstBlockAtInterval) =>
|
||||||
calculateNextWorkRequired(currentTip = tip, firstBlock, chainParams)
|
calculateNextWorkRequired(currentTip = tip,
|
||||||
|
firstBlockAtInterval,
|
||||||
|
chainParams)
|
||||||
case None =>
|
case None =>
|
||||||
Future.failed(
|
throw new RuntimeException(
|
||||||
new IllegalArgumentException(
|
s"Could not find block at height=${firstHeight} out of ${blockchain.length} headers to calculate pow difficutly change")
|
||||||
s"Could not find ancestor for block=${tip.hashBE.hex}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
powLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,22 +24,17 @@ 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")
|
||||||
|
@ -61,19 +52,16 @@ sealed abstract class TipValidation extends ChainVerificationLogger {
|
||||||
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)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue