Skip to content

Commit

Permalink
Use Long to back the UInt64 type (#1109)
Browse files Browse the repository at this point in the history
* Use Long to back the UInt64 type

* Define comparison operators between UInt64 and MilliSatoshi
  • Loading branch information
araspitzu authored Sep 4, 2019
1 parent 71da677 commit 5607b81
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 42 deletions.
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)
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 {

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) {
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") {
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))
}


}

0 comments on commit 5607b81

Please sign in to comment.