diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/MilliSatoshi.scala b/eclair-core/src/main/scala/fr/acinq/eclair/MilliSatoshi.scala index 1691125192..ff97d45565 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/MilliSatoshi.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/MilliSatoshi.scala @@ -16,7 +16,6 @@ package fr.acinq.eclair -import com.google.common.primitives.UnsignedLongs import fr.acinq.bitcoin.{Btc, BtcAmount, MilliBtc, Satoshi, btc2satoshi, millibtc2satoshi} /** @@ -41,7 +40,6 @@ case class MilliSatoshi(private val underlying: Long) extends Ordered[MilliSatos override def compare(other: MilliSatoshi): Int = underlying.compareTo(other.underlying) // Since BtcAmount is a sealed trait that MilliSatoshi cannot extend, we need to redefine comparison operators. def compare(other: BtcAmount): Int = compare(other.toMilliSatoshi) - def compareUnsigned(other: UInt64): Int = UnsignedLongs.compare(underlying, other.toBigInt.longValue()) def <=(other: BtcAmount): Boolean = compare(other) <= 0 def >=(other: BtcAmount): Boolean = compare(other) >= 0 def <(other: BtcAmount): Boolean = compare(other) < 0 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala b/eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala index c643c956ae..5a05c3bf43 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala @@ -16,34 +16,35 @@ package fr.acinq.eclair -import java.math.BigInteger - +import com.google.common.primitives.UnsignedLongs import scodec.bits.ByteVector +import scodec.bits.HexStringSyntax -case class UInt64(private val underlying: BigInt) extends Ordered[UInt64] { +case class UInt64(private val underlying: Long) extends Ordered[UInt64] { - require(underlying >= 0, s"uint64 must be positive (actual=$underlying)") - require(underlying <= UInt64.MaxValueBigInt, s"uint64 must be < 2^64 -1 (actual=$underlying)") + override def compare(o: UInt64): Int = UnsignedLongs.compare(underlying, o.underlying) + private def compare(other: MilliSatoshi): Int = other.toLong match { + case l if l < 0 => 1 // if @param 'other' is negative then is always smaller than 'this' + case _ => compare(UInt64(other.toLong)) // we must do an unsigned comparison here because the uint64 can exceed the capacity of MilliSatoshi class + } - override def compare(o: UInt64): Int = underlying.compare(o.underlying) + def <(other: MilliSatoshi): Boolean = compare(other) < 0 + def >(other: MilliSatoshi): Boolean = compare(other) > 0 + def <=(other: MilliSatoshi): Boolean = compare(other) <= 0 + def >=(other: MilliSatoshi): Boolean = compare(other) >= 0 - def toByteVector: ByteVector = ByteVector.view(underlying.toByteArray.takeRight(8)) + def toByteVector: ByteVector = ByteVector.fromLong(underlying) - def toBigInt: BigInt = underlying + def toBigInt: BigInt = (BigInt(underlying >>> 1) << 1) + (underlying & 1) - override def toString: String = underlying.toString + override def toString: String = UnsignedLongs.toString(underlying, 10) } - object UInt64 { - private val MaxValueBigInt = BigInt(new BigInteger("ffffffffffffffff", 16)) + val MaxValue = UInt64(hex"0xffffffffffffffff") - val MaxValue = UInt64(MaxValueBigInt) - - def apply(bin: ByteVector) = new UInt64(new BigInteger(1, bin.toArray)) - - def apply(value: Long) = new UInt64(BigInt(value)) + def apply(bin: ByteVector): UInt64 = UInt64(bin.toLong(signed = false)) object Conversions { @@ -51,5 +52,4 @@ object UInt64 { implicit def longToUint64(l: Long) = UInt64(l) } - } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index d459059f87..35eed22370 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -149,8 +149,7 @@ object Commitments { val outgoingHtlcs = reduced.htlcs.filter(_.direction == IN) val htlcValueInFlight = outgoingHtlcs.map(_.add.amountMsat).sum - // we must use unsigned comparison here because 'maxHtlcValueInFlightMsat' is effectively an uint64 and can exceed the capacity of MilliSatoshi class - if (htlcValueInFlight.compareUnsigned(commitments1.remoteParams.maxHtlcValueInFlightMsat) > 0) { + if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) { // TODO: this should be a specific UPDATE error return Left(HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.remoteParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)) } @@ -185,8 +184,7 @@ object Commitments { val incomingHtlcs = reduced.htlcs.filter(_.direction == IN) val htlcValueInFlight = incomingHtlcs.map(_.add.amountMsat).sum - // we must use unsigned comparison here because 'maxHtlcValueInFlightMsat' is effectively an uint64 and can exceed the capacity of MilliSatoshi class - if (htlcValueInFlight.compareUnsigned(commitments1.localParams.maxHtlcValueInFlightMsat) > 0) { + if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) { throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala index 809210524f..ef388f3a6f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala @@ -18,21 +18,9 @@ package fr.acinq.eclair import fr.acinq.bitcoin.{Btc, MilliBtc, Satoshi} import org.scalatest.FunSuite -import scodec.bits._ - class CoinUtilsSpec extends FunSuite { - test("use unsigned comparison when comparing millisatoshis to uint64") { - assert(MilliSatoshi(123).compareUnsigned(UInt64(123)) == 0) - assert(MilliSatoshi(1234).compareUnsigned(UInt64(123)) > 0) - assert(MilliSatoshi(123).compareUnsigned(UInt64(1234)) < 0) - assert(MilliSatoshi(123).compareUnsigned(UInt64(hex"ffffffffffffffff")) < 0) - assert(MilliSatoshi(-123).compareUnsigned(UInt64(hex"ffffffffffffffff")) < 0) - assert(MilliSatoshi(Long.MaxValue).compareUnsigned(UInt64(hex"7fffffffffffffff")) == 0) // 7fffffffffffffff == Long.MaxValue - assert(MilliSatoshi(Long.MaxValue).compareUnsigned(UInt64(hex"8000000000000000")) < 0) // 8000000000000000 == Long.MaxValue + 1 - } - test("Convert string amount to the correct BtcAmount") { val am_btc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", BtcUnit.code) assert(am_btc == MilliSatoshi(100000000000L)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala index def42f49ba..dfa1e606a6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala @@ -22,23 +22,63 @@ import scodec.bits._ class UInt64Spec extends FunSuite { - test("handle values from 0 to 2^63-1") { + test("handle values from 0 to 2^64-1") { val a = UInt64(hex"0xffffffffffffffff") val b = UInt64(hex"0xfffffffffffffffe") val c = UInt64(42) val z = UInt64(0) + val l = UInt64(Long.MaxValue) + val l1 = UInt64(hex"8000000000000000") // Long.MaxValue + 1 + assert(a > b) + assert(a.toBigInt > b.toBigInt) assert(b < a) - assert(z < a && z < b && z < c) + assert(b.toBigInt < a.toBigInt) + assert(l.toBigInt < l1.toBigInt) + assert(z < a && z < b && z < c && z < l && c < l && l < l1 && l < b && l < a) assert(a == a) - assert(a.toByteVector === hex"0xffffffffffffffff") - assert(a.toString === "18446744073709551615") - assert(b.toByteVector === hex"0xfffffffffffffffe") + assert(a == UInt64.MaxValue) + + assert(l.toByteVector == hex"7fffffffffffffff") + assert(l.toString == Long.MaxValue.toString) + assert(l.toBigInt == BigInt(Long.MaxValue)) + + assert(l1.toByteVector == hex"8000000000000000") + assert(l1.toString == "9223372036854775808") + assert(l1.toBigInt == BigInt("9223372036854775808")) + + assert(a.toByteVector === hex"ffffffffffffffff") + assert(a.toString === "18446744073709551615") // 2^64 - 1 + assert(a.toBigInt === BigInt("18446744073709551615")) + + assert(b.toByteVector === hex"fffffffffffffffe") assert(b.toString === "18446744073709551614") - assert(c.toByteVector === hex"0x2a") + assert(b.toBigInt === BigInt("18446744073709551614")) + + assert(c.toByteVector === hex"00000000000002a") assert(c.toString === "42") - assert(z.toByteVector === hex"0x00") + assert(c.toBigInt === BigInt("42")) + + assert(z.toByteVector === hex"000000000000000") assert(z.toString === "0") + assert(z.toBigInt === BigInt("0")) + + assert(UInt64(hex"ff").toByteVector == hex"0000000000000ff") + assert(UInt64(hex"800").toByteVector == hex"000000000000800") } + test("use unsigned comparison when comparing millisatoshis to uint64") { + assert(UInt64(123) <= MilliSatoshi(123) && UInt64(123) >= MilliSatoshi(123)) + assert(UInt64(123) < MilliSatoshi(1234)) + assert(UInt64(1234) > MilliSatoshi(123)) + assert(UInt64(hex"ffffffffffffffff") > MilliSatoshi(123)) + assert(UInt64(hex"ffffffffffffffff") > MilliSatoshi(-123)) + assert(UInt64(hex"7ffffffffffffffe") < MilliSatoshi(Long.MaxValue)) // 7ffffffffffffffe == Long.MaxValue - 1 + assert(UInt64(hex"7fffffffffffffff") <= MilliSatoshi(Long.MaxValue) && UInt64(hex"7fffffffffffffff") >= MilliSatoshi(Long.MaxValue)) // 7fffffffffffffff == Long.MaxValue + assert(UInt64(hex"8000000000000000") > MilliSatoshi(Long.MaxValue)) // 8000000000000000 == Long.MaxValue + 1 + assert(UInt64(1) > MilliSatoshi(-1)) + assert(UInt64(0) > MilliSatoshi(Long.MinValue)) + } + + } \ No newline at end of file