Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Long to back the UInt64 type #1109

Merged
merged 10 commits into from
Sep 4, 2019
2 changes: 0 additions & 2 deletions eclair-core/src/main/scala/fr/acinq/eclair/MilliSatoshi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package fr.acinq.eclair

import com.google.common.primitives.UnsignedLongs
import fr.acinq.bitcoin.{Btc, BtcAmount, MilliBtc, Satoshi, btc2satoshi, millibtc2satoshi}

/**
Expand All @@ -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
Expand Down
34 changes: 17 additions & 17 deletions eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,40 @@

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)
t-bast marked this conversation as resolved.
Show resolved Hide resolved
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)
t-bast marked this conversation as resolved.
Show resolved Hide resolved
}


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 {
t-bast marked this conversation as resolved.
Show resolved Hide resolved

implicit def intToUint64(l: Int) = UInt64(l)

implicit def longToUint64(l: Long) = UInt64(l)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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) {
pm47 marked this conversation as resolved.
Show resolved Hide resolved
throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
}

Expand Down
12 changes: 0 additions & 12 deletions eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
54 changes: 47 additions & 7 deletions eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find!

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)
t-bast marked this conversation as resolved.
Show resolved Hide resolved

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
araspitzu marked this conversation as resolved.
Show resolved Hide resolved
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") {
t-bast marked this conversation as resolved.
Show resolved Hide resolved
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))
}


}