Implemented BIP 157 Block Filter Headers (#532)

This commit is contained in:
Nadav Kohen 2019-06-17 14:52:10 -05:00 committed by Chris Stewart
parent a87858247c
commit 67fb821b00
3 changed files with 79 additions and 12 deletions

View File

@ -1,6 +1,6 @@
package org.bitcoins.core.gcs
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.testkit.util.BitcoinSUnitTest
@ -14,20 +14,28 @@ class BlockFilterTest extends BitcoinSUnitTest {
// https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki#appendix-c-test-vectors
case class Bip158TestCase(
blockHeight: Int,
blockHash: DoubleSha256Digest,
blockHash: DoubleSha256DigestBE,
block: Block,
prevOutputScripts: Vector[ScriptPubKey],
// TODO prevHeader: BlockFilterHeader,
prevHeader: DoubleSha256DigestBE,
filter: GolombFilter,
// TODO header: BlockFilterHeader,
header: DoubleSha256DigestBE,
notes: String
) {
val clue: String = s"Test Notes: $notes"
def runTest(): org.scalatest.Assertion = {
val constructedFilter = BlockFilter(block, prevOutputScripts)
assert(constructedFilter.decodedHashes == filter.decodedHashes,
s"Test Notes: $notes")
assert(constructedFilter.decodedHashes == filter.decodedHashes, clue)
assert(constructedFilter.encodedData.bytes == filter.encodedData.bytes,
clue)
val constructedHeader = constructedFilter.getHeader(prevHeader.flip)
assert(constructedHeader.hash == header.flip, clue)
}
}
@ -37,23 +45,33 @@ class BlockFilterTest extends BitcoinSUnitTest {
def fromJsArray(array: JsArray): Bip158TestCase = {
val parseResult = for {
height <- array(0).validate[Int]
blockHash <- array(1).validate[String].map(DoubleSha256Digest.fromHex)
blockHash <- array(1).validate[String].map(DoubleSha256DigestBE.fromHex)
block <- array(2).validate[String].map(Block.fromHex)
scriptArray <- array(3).validate[JsArray]
scripts = parseScripts(scriptArray)
//prevHeader <- array(4).validate[String].map(BlockFilterHeader.fromHex)
prevHeader <- array(4)
.validate[String]
.map(DoubleSha256DigestBE.fromHex)
filter <- array(5)
.validate[String]
.map(BlockFilter.fromHex(_, blockHash))
.map(BlockFilter.fromHex(_, blockHash.flip))
//header <- array(6).validate[String].map(BlockFilterHeader.fromHex)
header <- array(6).validate[String].map(DoubleSha256DigestBE.fromHex)
notes <- array(7).validate[String]
} yield Bip158TestCase(height, blockHash, block, scripts, filter, notes)
} yield
Bip158TestCase(height,
blockHash,
block,
scripts,
prevHeader,
filter,
header,
notes)
parseResult.get
}

View File

@ -0,0 +1,28 @@
package org.bitcoins.core.gcs
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.util.CryptoUtil
/**
* Bip 157 Block Filter Headers which commit to a chain of block filters,
* much in the same way that block headers commit to a block chain
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#filter-headers]]
*/
case class FilterHeader(
filterHash: DoubleSha256Digest,
prevHeaderHash: DoubleSha256Digest) {
val hash: DoubleSha256Digest = {
CryptoUtil.doubleSHA256(filterHash.bytes ++ prevHeaderHash.bytes)
}
/** Given the next Block Filter, constructs the next Block Filter Header */
def nextHeader(nextFilter: GolombFilter): FilterHeader = {
FilterHeader(filterHash = nextFilter.hash, prevHeaderHash = this.hash)
}
/** Given the next Block Filter hash, constructs the next Block Filter Header */
def nextHeader(nextFilterHash: DoubleSha256Digest): FilterHeader = {
FilterHeader(filterHash = nextFilterHash, prevHeaderHash = this.hash)
}
}

View File

@ -12,7 +12,7 @@ import org.bitcoins.core.protocol.transaction.{
TransactionOutput
}
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.util.BitcoinSUtil
import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil}
import scodec.bits.{BitVector, ByteVector}
import scala.annotation.tailrec
@ -31,6 +31,27 @@ case class GolombFilter(
encodedData: BitVector) {
lazy val decodedHashes: Vector[UInt64] = GCS.golombDecodeSet(encodedData, p)
/** The hash of this serialized filter */
lazy val hash: DoubleSha256Digest = {
CryptoUtil.doubleSHA256(this.bytes)
}
/** Given the previous FilterHeader, constructs the header corresponding to this */
def getHeader(prevHeader: FilterHeader): FilterHeader = {
FilterHeader(filterHash = this.hash, prevHeaderHash = prevHeader.hash)
}
/** Given the previous FilterHeader hash, constructs the header corresponding to this */
def getHeader(prevHeaderHash: DoubleSha256Digest): FilterHeader = {
FilterHeader(filterHash = this.hash, prevHeaderHash = prevHeaderHash)
}
def bytes: ByteVector = {
n.bytes ++ encodedData.bytes
}
def hex: String = bytes.toHex
// TODO: Offer alternative that stops decoding when it finds out if data is there
def matchesHash(hash: UInt64): Boolean = {
@tailrec