Improve filter header verification (#3566)

* Improve filter header verification

* unit test
This commit is contained in:
rorp 2021-08-18 04:57:01 -07:00 committed by GitHub
parent 114fbf35fe
commit fa45c74c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 23 deletions

View File

@ -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 {

View File

@ -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[