From aeeb7f5586c4cc524fa35898c25bcd0140b41d07 Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Mon, 18 Mar 2019 21:40:22 -0700 Subject: [PATCH] server: Update the GolombCodedSet to hold the hex encoded filter --- .../com/xsn/explorer/gcs/GolombCodedSet.scala | 8 ++++--- .../com/xsn/explorer/gcs/GolombEncoding.scala | 24 ++++++++++++++++++- .../xsn/explorer/gcs/GolombEncodingSpec.scala | 9 ++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/server/app/com/xsn/explorer/gcs/GolombCodedSet.scala b/server/app/com/xsn/explorer/gcs/GolombCodedSet.scala index 820a514..89c3b42 100644 --- a/server/app/com/xsn/explorer/gcs/GolombCodedSet.scala +++ b/server/app/com/xsn/explorer/gcs/GolombCodedSet.scala @@ -6,12 +6,14 @@ class GolombCodedSet( val p: Int, val m: Int, val n: Int, - val data: List[UnsignedByte]) { + val hex: HexString) - def hex: HexString = { +object GolombCodedSet { + + def apply(p: Int, m: Int, n: Int, data: List[UnsignedByte]): GolombCodedSet = { val string = data.map(_.byte).map("%02x".format(_)).mkString("") HexString.from(string) match { - case Some(value) => value + case Some(value) => new GolombCodedSet(p = p, m = m, n = n, hex = value) case None => throw new RuntimeException("Unexpected error, unable to create hex value") } } diff --git a/server/app/com/xsn/explorer/gcs/GolombEncoding.scala b/server/app/com/xsn/explorer/gcs/GolombEncoding.scala index b936ddc..f9fe545 100644 --- a/server/app/com/xsn/explorer/gcs/GolombEncoding.scala +++ b/server/app/com/xsn/explorer/gcs/GolombEncoding.scala @@ -26,7 +26,7 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) { .map { bits => UnsignedByte.parse(bits.padTo(8, Bit.Zero)) } .toList - new GolombCodedSet( + GolombCodedSet.apply( p = p, m = m, n = words.size, @@ -115,6 +115,28 @@ class GolombEncoding(p: Int, m: Int, key: SipHashKey) { List.fill(size - bits.size)(Bit.Zero) ++ bits } + /** + * NOTE: This is a copy from https://github.com/btcsuite/btcutil/blob/master/gcs/gcs.go + * that is used for compatibility reasons, here we don't care about such optimizations + * because a filter is built once per block and never queried. + * + * Original docs: + * fastReduction calculates a mapping that's more ore less equivalent to: x mod N. + * + * However, instead of using a mod operation, which using a non-power of two + * will lead to slowness on many processors due to unnecessary division, we + * instead use a "multiply-and-shift" trick which eliminates all divisions, + * described in: + * https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + * + * * v * N >> log_2(N) + * + * In our case, using 64-bit integers, log_2 is 64. As most processors don't + * support 128-bit arithmetic natively, we'll be super portable and unfold the + * operation into several operations with 64-bit arithmetic. As inputs, we the + * number to reduce, and our modulus N divided into its high 32-bits and lower + * 32-bits. + */ private def fastReduction(v: BigInt, modulus: BigInt): BigInt = { val nHi = modulus >> 32 val nLo = modulus & 0xFFFFFFFFL diff --git a/server/test/com/xsn/explorer/gcs/GolombEncodingSpec.scala b/server/test/com/xsn/explorer/gcs/GolombEncodingSpec.scala index 572920f..0d8c265 100644 --- a/server/test/com/xsn/explorer/gcs/GolombEncodingSpec.scala +++ b/server/test/com/xsn/explorer/gcs/GolombEncodingSpec.scala @@ -1,5 +1,6 @@ package com.xsn.explorer.gcs +import com.google.common.io.BaseEncoding import org.scalatest.{MustMatchers, WordSpec} class GolombEncodingSpec extends WordSpec with MustMatchers { @@ -35,7 +36,13 @@ class GolombEncodingSpec extends WordSpec with MustMatchers { "decode the same hashes" in { val hashes = golomb.hashes(words) - val decoded = golomb.decode(encoded.data, words.size) + val bytes = BaseEncoding + .base16() + .decode(encoded.hex.string.toUpperCase) + .toList + .map(new UnsignedByte(_)) + + val decoded = golomb.decode(bytes, words.size) decoded mustEqual hashes }