mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
Improve filter header verification (#3566)
* Improve filter header verification * unit test
This commit is contained in:
parent
114fbf35fe
commit
fa45c74c36
@ -1,10 +1,14 @@
|
||||
package org.bitcoins.chain.blockchain
|
||||
|
||||
import org.bitcoins.chain.{ChainCallbacks, OnBlockHeaderConnected}
|
||||
import org.bitcoins.chain.pow.Pow
|
||||
import org.bitcoins.chain.{ChainCallbacks, OnBlockHeaderConnected}
|
||||
import org.bitcoins.core.api.chain.ChainApi
|
||||
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
|
||||
import org.bitcoins.core.api.chain.db.{BlockHeaderDb, BlockHeaderDbHelper}
|
||||
import org.bitcoins.core.api.chain.db.{
|
||||
BlockHeaderDb,
|
||||
BlockHeaderDbHelper,
|
||||
CompactFilterHeaderDb
|
||||
}
|
||||
import org.bitcoins.core.gcs.{BlockFilter, FilterHeader}
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.p2p.CompactFilterMessage
|
||||
@ -244,6 +248,75 @@ class ChainHandlerTest extends ChainDbUnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
it must "verify a batch of filter headers" in { chainHandler: ChainHandler =>
|
||||
val goodBatch = Vector(
|
||||
ChainUnitTest.genesisFilterHeaderDb,
|
||||
CompactFilterHeaderDb(
|
||||
hashBE = DoubleSha256DigestBE.fromHex(
|
||||
"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"),
|
||||
previousFilterHeaderBE = ChainUnitTest.genesisFilterHeaderDb.hashBE,
|
||||
height = 1,
|
||||
filterHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"555152535455565758595a5b5c5d5e5f555152535455565758595a5b5c5d5e5f"),
|
||||
blockHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"aaa1a2a3a4a5a6a7a8a9aaabacadaeafaaa1a2a3a4a5a6a7a8a9aaabacadaeaf")
|
||||
)
|
||||
)
|
||||
|
||||
val invalidGenesisFilterHeaderBatch = Vector(
|
||||
ChainUnitTest.genesisFilterHeaderDb.copy(
|
||||
hashBE = ChainUnitTest.genesisFilterHeaderDb.previousFilterHeaderBE,
|
||||
previousFilterHeaderBE = ChainUnitTest.genesisFilterHeaderDb.hashBE
|
||||
)
|
||||
)
|
||||
|
||||
val invalidFilterHeaderBatch = Vector(
|
||||
ChainUnitTest.genesisFilterHeaderDb.copy(height = 1)
|
||||
)
|
||||
|
||||
val selfReferenceFilterHeaderBatch = Vector(
|
||||
ChainUnitTest.genesisFilterHeaderDb,
|
||||
CompactFilterHeaderDb(
|
||||
hashBE = DoubleSha256DigestBE.fromHex(
|
||||
"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"),
|
||||
previousFilterHeaderBE = DoubleSha256DigestBE.fromHex(
|
||||
"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"),
|
||||
height = 1,
|
||||
filterHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"555152535455565758595a5b5c5d5e5f555152535455565758595a5b5c5d5e5f"),
|
||||
blockHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"aaa1a2a3a4a5a6a7a8a9aaabacadaeafaaa1a2a3a4a5a6a7a8a9aaabacadaeaf")
|
||||
)
|
||||
)
|
||||
|
||||
val unknownFilterHeaderBatch = Vector(
|
||||
ChainUnitTest.genesisFilterHeaderDb,
|
||||
CompactFilterHeaderDb(
|
||||
hashBE = DoubleSha256DigestBE.fromHex(
|
||||
"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"),
|
||||
previousFilterHeaderBE = DoubleSha256DigestBE.fromHex(
|
||||
"555152535455565758595a5b5c5d5e5f555152535455565758595a5b5c5d5e5f"),
|
||||
height = 1,
|
||||
filterHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"555152535455565758595a5b5c5d5e5f555152535455565758595a5b5c5d5e5f"),
|
||||
blockHashBE = DoubleSha256DigestBE.fromHex(
|
||||
"aaa1a2a3a4a5a6a7a8a9aaabacadaeafaaa1a2a3a4a5a6a7a8a9aaabacadaeaf")
|
||||
)
|
||||
)
|
||||
|
||||
for {
|
||||
_ <- chainHandler.verifyFilterHeaders(goodBatch)
|
||||
_ <- recoverToSucceededIf[IllegalArgumentException](
|
||||
chainHandler.verifyFilterHeaders(invalidGenesisFilterHeaderBatch))
|
||||
_ <- recoverToSucceededIf[IllegalArgumentException](
|
||||
chainHandler.verifyFilterHeaders(invalidFilterHeaderBatch))
|
||||
_ <- recoverToSucceededIf[IllegalArgumentException](
|
||||
chainHandler.verifyFilterHeaders(selfReferenceFilterHeaderBatch))
|
||||
_ <- recoverToSucceededIf[IllegalArgumentException](
|
||||
chainHandler.verifyFilterHeaders(unknownFilterHeaderBatch))
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
it must "get the highest filter" in { chainHandler: ChainHandler =>
|
||||
{
|
||||
for {
|
||||
|
@ -352,27 +352,7 @@ class ChainHandler(
|
||||
|
||||
for {
|
||||
filterHeadersToCreate <- filterHeadersToCreateF
|
||||
_ <-
|
||||
if (
|
||||
filterHeadersToCreate.nonEmpty && filterHeadersToCreate.head.height > 0
|
||||
) {
|
||||
val firstFilter = filterHeadersToCreate.head
|
||||
val filterHashFOpt = filterHeaderDAO
|
||||
.findByHash(firstFilter.previousFilterHeaderBE)
|
||||
filterHashFOpt.map {
|
||||
case Some(prevHeader) =>
|
||||
require(
|
||||
prevHeader.height == firstFilter.height - 1,
|
||||
s"Unexpected previous header's height: ${prevHeader.height} != ${filterHeadersToCreate.head.height - 1}"
|
||||
)
|
||||
case None =>
|
||||
// If the previous filter header doesn't exist it must be for the genesis block
|
||||
require(
|
||||
firstFilter.previousFilterHeaderBE == DoubleSha256DigestBE.empty && firstFilter.height == 0,
|
||||
s"Previous filter header does not exist: $firstFilter"
|
||||
)
|
||||
}
|
||||
} else Future.unit
|
||||
_ <- verifyFilterHeaders(filterHeadersToCreate)
|
||||
_ <- filterHeaderDAO.createAll(filterHeadersToCreate)
|
||||
} yield {
|
||||
val minHeightOpt = filterHeadersToCreate.minByOption(_.height)
|
||||
@ -437,6 +417,66 @@ class ChainHandler(
|
||||
}
|
||||
}
|
||||
|
||||
/** Verifies if the previous headers exist either in the batch [[filterHeaders]]
|
||||
* or in the database, throws if it doesn't
|
||||
*/
|
||||
def verifyFilterHeaders(
|
||||
filterHeaders: Vector[CompactFilterHeaderDb]): Future[Unit] = {
|
||||
val byHash = filterHeaders.foldLeft(
|
||||
Map.empty[DoubleSha256DigestBE, CompactFilterHeaderDb])((acc, fh) =>
|
||||
acc.updated(fh.hashBE, fh))
|
||||
val verify = checkFilterHeader(byHash)(_)
|
||||
FutureUtil.sequentially(filterHeaders)(verify).map(_ => ())
|
||||
}
|
||||
|
||||
private def checkFilterHeader(
|
||||
filtersByHash: Map[DoubleSha256DigestBE, CompactFilterHeaderDb])(
|
||||
filterHeader: CompactFilterHeaderDb): Future[Unit] = {
|
||||
|
||||
def checkHeight(
|
||||
filterHeader: CompactFilterHeaderDb,
|
||||
prevHeader: CompactFilterHeaderDb): Unit = {
|
||||
require(
|
||||
prevHeader.height == filterHeader.height - 1,
|
||||
s"Unexpected previous filter header's height: ${prevHeader.height} != ${filterHeader.height - 1}"
|
||||
)
|
||||
}
|
||||
|
||||
if (filterHeader.hashBE == filterHeader.previousFilterHeaderBE) {
|
||||
Future.failed(
|
||||
new IllegalArgumentException(
|
||||
s"Filter header cannot reference to itself: ${filterHeader}"))
|
||||
} else if (filterHeader.height == 0) {
|
||||
Future {
|
||||
require(
|
||||
filterHeader.previousFilterHeaderBE == DoubleSha256DigestBE.empty,
|
||||
s"Previous filter header hash for the genesis block must be empty: ${filterHeader}")
|
||||
}
|
||||
} else {
|
||||
if (filterHeader.previousFilterHeaderBE == DoubleSha256DigestBE.empty) {
|
||||
Future.failed(new IllegalArgumentException(
|
||||
s"Previous filter header hash for a regular block must not be empty: ${filterHeader}"))
|
||||
} else {
|
||||
filtersByHash.get(filterHeader.previousFilterHeaderBE) match {
|
||||
case Some(prevHeader) =>
|
||||
Future {
|
||||
checkHeight(filterHeader, prevHeader)
|
||||
}
|
||||
case None =>
|
||||
val filterHashFOpt = filterHeaderDAO
|
||||
.findByHash(filterHeader.previousFilterHeaderBE)
|
||||
filterHashFOpt.map {
|
||||
case Some(prevHeader) =>
|
||||
checkHeight(filterHeader, prevHeader)
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Previous filter header does not exist: $filterHeader")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def findFilterDbFromMessage(
|
||||
filterHeader: CompactFilterHeaderDb,
|
||||
messagesByBlockHash: Map[
|
||||
|
Loading…
Reference in New Issue
Block a user