mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-15 20:30:17 +01:00
2020 03 15 chainhandler getnancestor (#1247)
* Rename BlockHeaderDAO.getNChildren() to BlockHeaderDAO.getNAncestor() as that is what it actually does * This changes the behavior of BlockHeaderDAO.getNAncestors from the previous implementation which just fetched headers in between two heights, to actually validating the headers that are fetched from the database with Blockchain.reconstructFromheaders() * Run scalafmt * Replace BlockHeader.getNAncestors call to Blockchain.reconstructFromHeaders() with Blockchain.connectWalkBackwards() so that we don't do full POW validation from the database, just make sure the headers connect to avoid reorg problems. Also add some more informative logs * Address Roman's code review
This commit is contained in:
parent
277d62591f
commit
800fdffffb
8 changed files with 198 additions and 23 deletions
|
@ -1,23 +1,25 @@
|
|||
package org.bitcoins.chain.blockchain
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
||||
import org.bitcoins.testkit.chain.fixture.ChainFixture
|
||||
import org.bitcoins.testkit.chain.{BlockHeaderHelper, ChainUnitTest}
|
||||
import org.scalatest.FutureOutcome
|
||||
|
||||
class BlockchainTest extends ChainUnitTest {
|
||||
import scala.collection.mutable
|
||||
|
||||
override type FixtureParam = BlockHeaderDAO
|
||||
class BlockchainTest extends ChainUnitTest {
|
||||
override type FixtureParam = ChainFixture
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
||||
withBlockHeaderDAO(test)
|
||||
withChainFixture(test)
|
||||
|
||||
implicit override val system: ActorSystem = ActorSystem("BlockchainTest")
|
||||
|
||||
behavior of "Blockchain"
|
||||
|
||||
it must "connect a new header to the current tip of a blockchain" in {
|
||||
bhDAO: BlockHeaderDAO =>
|
||||
it must "connect a new header to the current tip of a blockchain" inFixtured {
|
||||
case ChainFixture.Empty =>
|
||||
val blockchain = Blockchain.fromHeaders(
|
||||
headers = Vector(ChainUnitTest.genesisHeaderDb)
|
||||
)
|
||||
|
@ -36,4 +38,43 @@ class BlockchainTest extends ChainUnitTest {
|
|||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
it must "reconstruct a blockchain given a child header correctly" inFixtured {
|
||||
case ChainFixture.Empty =>
|
||||
val accum = new mutable.ArrayBuffer[BlockHeaderDb](5)
|
||||
accum.+=(ChainUnitTest.genesisHeaderDb)
|
||||
//generate 4 headers
|
||||
0.until(4).foreach { _ =>
|
||||
val newHeader = BlockHeaderHelper.buildNextHeader(accum.last)
|
||||
accum.+=(newHeader)
|
||||
}
|
||||
|
||||
//now given the last header, and the other headers we should reconstruct the blockchain
|
||||
val headers = accum.dropRight(1).toVector
|
||||
val tip = accum.last
|
||||
|
||||
val reconstructed = Blockchain.reconstructFromHeaders(childHeader = tip,
|
||||
ancestors = headers)
|
||||
|
||||
assert(reconstructed.length == 1)
|
||||
val chain = reconstructed.head
|
||||
assert(chain.toVector.length == 5)
|
||||
assert(chain.tip == accum.last)
|
||||
assert(chain.last == ChainUnitTest.genesisHeaderDb)
|
||||
assert(chain.toVector == accum.reverse.toVector)
|
||||
}
|
||||
|
||||
it must "fail to reconstruct a blockchain if we do not have validly connected headers" inFixtured {
|
||||
case ChainFixture.Empty =>
|
||||
val missingHeader =
|
||||
BlockHeaderHelper.buildNextHeader(ChainUnitTest.genesisHeaderDb)
|
||||
|
||||
val thirdHeader = BlockHeaderHelper.buildNextHeader(missingHeader)
|
||||
|
||||
val reconstructed =
|
||||
Blockchain.reconstructFromHeaders(thirdHeader,
|
||||
Vector(ChainUnitTest.genesisHeaderDb))
|
||||
|
||||
assert(reconstructed.isEmpty)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.bitcoins.chain.models
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||
import org.bitcoins.testkit.chain.{BlockHeaderHelper, ChainUnitTest}
|
||||
import org.scalatest.FutureOutcome
|
||||
|
||||
|
@ -201,4 +202,53 @@ class BlockHeaderDAOTest extends ChainUnitTest {
|
|||
assert(genesisOpt.contains(genesisHeaderDb))
|
||||
}
|
||||
}
|
||||
|
||||
it must "implement getNAncestors correctly" in {
|
||||
blockHeaderDAO: BlockHeaderDAO =>
|
||||
val blockHeader = BlockHeaderHelper.buildNextHeader(genesisHeaderDb)
|
||||
val createdF = blockHeaderDAO.create(blockHeader)
|
||||
|
||||
val noGenesisAncestorsF =
|
||||
blockHeaderDAO.getNAncestors(childHash =
|
||||
genesisHeaderDb.blockHeader.hashBE,
|
||||
n = 1)
|
||||
|
||||
val foundGenesisF =
|
||||
blockHeaderDAO.getNAncestors(childHash =
|
||||
genesisHeaderDb.blockHeader.hashBE,
|
||||
n = 0)
|
||||
val emptyAssertion = for {
|
||||
noGenesisAncestors <- noGenesisAncestorsF
|
||||
foundGenesis <- foundGenesisF
|
||||
} yield {
|
||||
assert(noGenesisAncestors.length == 1)
|
||||
assert(noGenesisAncestors == Vector(ChainUnitTest.genesisHeaderDb))
|
||||
|
||||
assert(foundGenesis.length == 1)
|
||||
assert(foundGenesis == Vector(ChainUnitTest.genesisHeaderDb))
|
||||
}
|
||||
|
||||
val oneChildF = for {
|
||||
created <- createdF
|
||||
children <- blockHeaderDAO.getNAncestors(childHash =
|
||||
created.blockHeader.hashBE,
|
||||
n = 1)
|
||||
} yield {
|
||||
assert(children.length == 2)
|
||||
assert(children == Vector(created, genesisHeaderDb))
|
||||
}
|
||||
|
||||
val bashHash = DoubleSha256DigestBE.empty
|
||||
val hashDoesNotExistF = for {
|
||||
children <- blockHeaderDAO.getNAncestors(childHash = bashHash, n = 1)
|
||||
} yield {
|
||||
assert(children.isEmpty)
|
||||
}
|
||||
|
||||
for {
|
||||
_ <- emptyAssertion
|
||||
_ <- oneChildF
|
||||
lastAssert <- hashDoesNotExistF
|
||||
} yield lastAssert
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ private[blockchain] trait BaseBlockChainCompObject
|
|||
//found a header to connect to!
|
||||
val prevBlockHeader = blockchain.headers(prevHeaderIdx)
|
||||
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 of ${blockchain.length} headers")
|
||||
val chain = blockchain.fromValidHeader(prevBlockHeader)
|
||||
val tipResult =
|
||||
TipValidation.checkNewTip(newPotentialTip = header, chain)
|
||||
|
@ -234,4 +234,62 @@ private[blockchain] trait BaseBlockChainCompObject
|
|||
}
|
||||
}
|
||||
|
||||
/** Walks backwards from the current header searching through ancestors if [[current.previousBlockHashBE]] is in [[ancestors]]
|
||||
* This does not validate other things such as POW.
|
||||
* */
|
||||
@tailrec
|
||||
final def connectWalkBackwards(
|
||||
current: BlockHeaderDb,
|
||||
ancestors: Vector[BlockHeaderDb],
|
||||
accum: Vector[BlockHeaderDb] = Vector.empty)(
|
||||
implicit chainAppConfig: ChainAppConfig): Vector[BlockHeaderDb] = {
|
||||
val prevHeaderOpt = ancestors.find(_.hashBE == current.previousBlockHashBE)
|
||||
prevHeaderOpt match {
|
||||
case Some(h) =>
|
||||
connectWalkBackwards(current = h,
|
||||
accum = current +: accum,
|
||||
ancestors = ancestors)
|
||||
case None =>
|
||||
logger.debug(s"No prev found for $current hashBE=${current.hashBE}")
|
||||
current +: accum
|
||||
}
|
||||
}
|
||||
|
||||
/** Walks backwards from a child header reconstructing a blockchain
|
||||
* This validates things like POW, difficulty change etc.
|
||||
* */
|
||||
def reconstructFromHeaders(
|
||||
childHeader: BlockHeaderDb,
|
||||
ancestors: Vector[BlockHeaderDb])(
|
||||
implicit chainAppConfig: ChainAppConfig): Vector[Blockchain] = {
|
||||
//now all hashes are connected correctly forming a
|
||||
//valid blockchain in term of hashes connected to each other
|
||||
val orderedHeaders = connectWalkBackwards(current = childHeader,
|
||||
accum = Vector.empty,
|
||||
ancestors = ancestors)
|
||||
|
||||
val initBlockchainOpt = orderedHeaders match {
|
||||
case Vector() | _ +: Vector() =>
|
||||
//for the case of _ +: Vector() this means only our
|
||||
//child header is in the chain, which means we
|
||||
//weren't able to form a blockchain
|
||||
None
|
||||
case h +: _ =>
|
||||
//find our first header as we need it's Db representation
|
||||
//rather than just the raw header
|
||||
val dbOpt = ancestors.find(_.hashBE == h.hashBE)
|
||||
Some(Blockchain.fromHeaders(Vector(dbOpt.get)))
|
||||
}
|
||||
|
||||
//now let's connect headers
|
||||
val blockchainUpdateOpt = initBlockchainOpt.map { initBlockchain =>
|
||||
Blockchain.connectHeadersToChains(orderedHeaders.tail.map(_.blockHeader),
|
||||
Vector(initBlockchain))
|
||||
}
|
||||
|
||||
blockchainUpdateOpt match {
|
||||
case Some(v) => v.map(_.blockchain)
|
||||
case None => Vector.empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ case class ChainHandler(
|
|||
|
||||
val filterHeadersToCreateF: Future[Vector[CompactFilterHeaderDb]] = for {
|
||||
blockHeaders <- blockHeaderDAO
|
||||
.getNChildren(ancestorHash = stopHash, n = filterHeaders.size - 1)
|
||||
.getNAncestors(childHash = stopHash, n = filterHeaders.size - 1)
|
||||
.map(_.sortBy(_.height))
|
||||
} yield {
|
||||
if (blockHeaders.size != filterHeaders.size) {
|
||||
|
|
|
@ -141,20 +141,29 @@ case class BlockHeaderDAO()(
|
|||
}
|
||||
}
|
||||
|
||||
/** Gets Block Headers of all children starting with the given block hash (inclusive), could be out of order */
|
||||
def getNChildren(
|
||||
ancestorHash: DoubleSha256DigestBE,
|
||||
/** Gets ancestor block headers starting with the given block hash (inclusive)
|
||||
* These headers are guaranteed to be in order and a valid chain.
|
||||
* */
|
||||
def getNAncestors(
|
||||
childHash: DoubleSha256DigestBE,
|
||||
n: Int): Future[Vector[BlockHeaderDb]] = {
|
||||
logger.debug(s"Getting $n ancestors for blockhash=$childHash")
|
||||
val headerOptF = findByHash(childHash)
|
||||
for {
|
||||
headerOpt <- findByHash(ancestorHash)
|
||||
res <- headerOpt match {
|
||||
headerOpt <- headerOptF
|
||||
headers <- headerOpt match {
|
||||
case Some(header) =>
|
||||
getBetweenHeights(Math.max(header.height - n, 0), header.height)
|
||||
val startHeight = Math.max(header.height - n, 0)
|
||||
getBetweenHeights(startHeight, header.height)
|
||||
case None =>
|
||||
Future.successful(Vector.empty)
|
||||
}
|
||||
} yield {
|
||||
res
|
||||
headerOpt.map { header =>
|
||||
val connectedHeaders =
|
||||
Blockchain.connectWalkBackwards(header, headers)
|
||||
connectedHeaders.reverse
|
||||
}.getOrElse(Vector.empty)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package org.bitcoins.chain.pow
|
||||
|
||||
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.chain.models.BlockHeaderDb
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.blockchain._
|
||||
import org.bitcoins.core.util.NumberUtil
|
||||
|
||||
/**
|
||||
|
@ -42,10 +42,16 @@ sealed abstract class Pow {
|
|||
|
||||
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}")
|
||||
case None =>
|
||||
config.chain match {
|
||||
case RegTestNetChainParams =>
|
||||
RegTestNetChainParams.compressedPowLimit
|
||||
case TestNetChainParams | MainNetChainParams =>
|
||||
//if we can't find a non min diffulty block, let's just fail
|
||||
throw new RuntimeException(
|
||||
s"Could not find non mindifficulty block in chain of size=${blockchain.length}! hash=${tip.hashBE.hex} height=${currentHeight}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1047,6 +1047,17 @@ case class CompactFilterCheckPointMessage(
|
|||
|
||||
def bytes: ByteVector =
|
||||
RawCompactFilterCheckpointMessageSerializer.write(this)
|
||||
|
||||
override def toString: String = {
|
||||
val headersString = {
|
||||
if (filterHeaders.isEmpty) {
|
||||
"empty"
|
||||
} else {
|
||||
s"${filterHeaders.head}...${filterHeaders.last}"
|
||||
}
|
||||
}
|
||||
s"CompactFilterCheckPointMessage(filterType=${filterType}, stopHash=${stopHash}, filterHeaders=${headersString})"
|
||||
}
|
||||
}
|
||||
|
||||
object CompactFilterCheckPointMessage
|
||||
|
|
|
@ -25,7 +25,7 @@ object BytesToPushOntoStack
|
|||
override val opCode = num
|
||||
}
|
||||
|
||||
override def operations: Seq[BytesToPushOntoStack] =
|
||||
override val operations: Seq[BytesToPushOntoStack] =
|
||||
(for { i <- 0 to 75 } yield BytesToPushOntoStackImpl(i))
|
||||
|
||||
def fromNumber(num: Long): BytesToPushOntoStack = {
|
||||
|
|
Loading…
Add table
Reference in a new issue