Skip to content

Commit 9c595e3

Browse files
committed
Combine feature in codec, not type
This allows us to drop the global/local fields from the codebase. They are handled directly at the wire level.
1 parent 120f773 commit 9c595e3

26 files changed

Lines changed: 117 additions & 115 deletions

eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
307307
paymentBasepoint = open.paymentBasepoint,
308308
delayedPaymentBasepoint = open.delayedPaymentBasepoint,
309309
htlcBasepoint = open.htlcBasepoint,
310-
globalFeatures = remoteInit.globalFeatures,
311-
localFeatures = remoteInit.localFeatures)
310+
features = remoteInit.features)
312311
log.debug(s"remote params: $remoteParams")
313312
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, channelVersion, accept) sending accept
314313
}
@@ -340,8 +339,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
340339
paymentBasepoint = accept.paymentBasepoint,
341340
delayedPaymentBasepoint = accept.delayedPaymentBasepoint,
342341
htlcBasepoint = accept.htlcBasepoint,
343-
globalFeatures = remoteInit.globalFeatures,
344-
localFeatures = remoteInit.localFeatures)
342+
features = remoteInit.features)
345343
log.debug(s"remote params: $remoteParams")
346344
val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
347345
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey)))

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,7 @@ final case class LocalParams(nodeId: PublicKey,
212212
maxAcceptedHtlcs: Int,
213213
isFunder: Boolean,
214214
defaultFinalScriptPubKey: ByteVector,
215-
globalFeatures: ByteVector,
216-
localFeatures: ByteVector)
215+
features: ByteVector)
217216

218217
final case class RemoteParams(nodeId: PublicKey,
219218
dustLimit: Satoshi,
@@ -227,8 +226,7 @@ final case class RemoteParams(nodeId: PublicKey,
227226
paymentBasepoint: PublicKey,
228227
delayedPaymentBasepoint: PublicKey,
229228
htlcBasepoint: PublicKey,
230-
globalFeatures: ByteVector,
231-
localFeatures: ByteVector)
229+
features: ByteVector)
232230

233231
object ChannelFlags {
234232
val AnnounceChannel = 0x01.toByte

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ object Helpers {
6767
*/
6868
def updateFeatures(data: HasCommitments, localInit: Init, remoteInit: Init): HasCommitments = {
6969
val commitments1 = data.commitments.copy(
70-
localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures),
71-
remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures))
70+
localParams = data.commitments.localParams.copy(features = localInit.features),
71+
remoteParams = data.commitments.remoteParams.copy(features = remoteInit.features))
7272
data match {
7373
case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = commitments1)
7474
case d: DATA_WAIT_FOR_FUNDING_LOCKED => d.copy(commitments = commitments1)
@@ -173,25 +173,25 @@ object Helpers {
173173
}
174174

175175
/**
176-
*
177-
* @param referenceFeePerKw reference fee rate per kiloweight
178-
* @param currentFeePerKw current fee rate per kiloweight
179-
* @return the "normalized" difference between i.e local and remote fee rate: |reference - current| / avg(current, reference)
180-
*/
176+
*
177+
* @param referenceFeePerKw reference fee rate per kiloweight
178+
* @param currentFeePerKw current fee rate per kiloweight
179+
* @return the "normalized" difference between i.e local and remote fee rate: |reference - current| / avg(current, reference)
180+
*/
181181
def feeRateMismatch(referenceFeePerKw: Long, currentFeePerKw: Long): Double =
182182
Math.abs((2.0 * (referenceFeePerKw - currentFeePerKw)) / (currentFeePerKw + referenceFeePerKw))
183183

184184
def shouldUpdateFee(commitmentFeeratePerKw: Long, networkFeeratePerKw: Long, updateFeeMinDiffRatio: Double): Boolean =
185185
feeRateMismatch(networkFeeratePerKw, commitmentFeeratePerKw) > updateFeeMinDiffRatio
186186

187187
/**
188-
*
189-
* @param referenceFeePerKw reference fee rate per kiloweight
190-
* @param currentFeePerKw current fee rate per kiloweight
191-
* @param maxFeerateMismatchRatio maximum fee rate mismatch ratio
192-
* @return true if the difference between current and reference fee rates is too high.
193-
* the actual check is |reference - current| / avg(current, reference) > mismatch ratio
194-
*/
188+
*
189+
* @param referenceFeePerKw reference fee rate per kiloweight
190+
* @param currentFeePerKw current fee rate per kiloweight
191+
* @param maxFeerateMismatchRatio maximum fee rate mismatch ratio
192+
* @return true if the difference between current and reference fee rates is too high.
193+
* the actual check is |reference - current| / avg(current, reference) > mismatch ratio
194+
*/
195195
def isFeeDiffTooHigh(referenceFeePerKw: Long, currentFeePerKw: Long, maxFeerateMismatchRatio: Double): Boolean =
196196
feeRateMismatch(referenceFeePerKw, currentFeePerKw) > maxFeerateMismatchRatio
197197

@@ -459,7 +459,7 @@ object Helpers {
459459
// TODO: check that
460460
val dustLimitSatoshis = localParams.dustLimit.max(remoteParams.dustLimit)
461461
val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec)
462-
val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
462+
val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
463463
val closingSigned = ClosingSigned(channelId, closingFee, localClosingSig)
464464
log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}")
465465
log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}")

eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A
101101
transport ! TransportHandler.Listener(self)
102102
context watch transport
103103
val localInit = nodeParams.overrideFeatures.get(remoteNodeId) match {
104-
case Some(f) => wire.Init(ByteVector.empty, f)
105-
case None => wire.Init(ByteVector.empty, nodeParams.features)
104+
case Some(f) => wire.Init(f)
105+
case None => wire.Init(nodeParams.features)
106106
}
107-
log.info(s"using globalFeatures=${localInit.globalFeatures.toBin} and localFeatures=${localInit.localFeatures.toBin}")
107+
log.info(s"using features=${localInit.features.toBin}")
108108
transport ! localInit
109109

110110
val address_opt = if (outgoing) {
@@ -134,7 +134,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A
134134
case Event(remoteInit: wire.Init, d: InitializingData) =>
135135
d.transport ! TransportHandler.ReadAck(remoteInit)
136136

137-
log.info(s"peer is using globalFeatures=${remoteInit.globalFeatures.toBin} and localFeatures=${remoteInit.localFeatures.toBin}")
137+
log.info(s"peer is using features=${remoteInit.features.toBin}")
138138

139139
if (Features.areSupported(remoteInit.features)) {
140140
d.origin_opt.foreach(origin => origin ! "connected")
@@ -680,8 +680,7 @@ object Peer {
680680
maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs,
681681
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
682682
isFunder = isFunder,
683-
globalFeatures = ByteVector.empty,
684-
localFeatures = nodeParams.features)
683+
features = nodeParams.features)
685684
}
686685

687686
/**

eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ object ChannelCodecs extends Logging {
7070
("maxAcceptedHtlcs" | uint16) ::
7171
("isFunder" | bool) ::
7272
("defaultFinalScriptPubKey" | varsizebinarydata) ::
73-
("globalFeatures" | varsizebinarydata) ::
74-
("localFeatures" | varsizebinarydata)).as[LocalParams]
73+
("features" | combinedFeaturesCodec)).as[LocalParams]
7574

7675
val remoteParamsCodec: Codec[RemoteParams] = (
7776
("nodeId" | publicKey) ::
@@ -86,8 +85,7 @@ object ChannelCodecs extends Logging {
8685
("paymentBasepoint" | publicKey) ::
8786
("delayedPaymentBasepoint" | publicKey) ::
8887
("htlcBasepoint" | publicKey) ::
89-
("globalFeatures" | varsizebinarydata) ::
90-
("localFeatures" | varsizebinarydata)).as[RemoteParams]
88+
("features" | combinedFeaturesCodec)).as[RemoteParams]
9189

9290
val directionCodec: Codec[Direction] = Codec[Direction](
9391
(dir: Direction) => bool.encode(dir == IN),

eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,34 @@ import fr.acinq.eclair.wire.CommonCodecs._
2020
import fr.acinq.eclair.{KamonExt, wire}
2121
import kamon.Kamon
2222
import kamon.tag.TagSet
23-
import scodec.bits.BitVector
23+
import scodec.bits.{BitVector, ByteVector}
2424
import scodec.codecs._
2525
import scodec.{Attempt, Codec}
26+
import shapeless.HNil
2627

2728
/**
2829
* Created by PM on 15/11/2016.
2930
*/
3031
object LightningMessageCodecs {
3132

32-
val initCodec: Codec[Init] = (
33-
("globalFeatures" | varsizebinarydata) ::
34-
("localFeatures" | varsizebinarydata)).as[Init]
33+
/** For historical reasons, features are divided into two feature bitmasks. We only send from the second one, but we allow receiving in both. */
34+
val combinedFeaturesCodec: Codec[ByteVector] = (("globalFeatures" | varsizebinarydata) :: ("localFeatures" | varsizebinarydata)).exmap[ByteVector](
35+
features => {
36+
val f1 = features.head
37+
val f2 = features.tail.head
38+
val combinedFeatures = if (f1.length == f2.length) {
39+
f1 | f2
40+
} else if (f1.length > f2.length) {
41+
f1 | f2.padLeft(f1.length)
42+
} else {
43+
f1.padLeft(f2.length) | f2
44+
}
45+
Attempt.Successful(combinedFeatures)
46+
},
47+
features => Attempt.Successful(ByteVector.empty :: features :: HNil)
48+
)
49+
50+
val initCodec: Codec[Init] = combinedFeaturesCodec.as[Init]
3551

3652
val errorCodec: Codec[Error] = (
3753
("channelId" | bytes32) ::

eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,7 @@ sealed trait HasChainHash extends LightningMessage { def chainHash: ByteVector32
4545
sealed trait UpdateMessage extends HtlcMessage // <- not in the spec
4646
// @formatter:on
4747

48-
/** For historical reasons, features are divided into two feature bitmasks. We only use the second one in our messages, but we allow receiving in both. */
49-
case class Init(globalFeatures: ByteVector, localFeatures: ByteVector) extends SetupMessage {
50-
val features = combineFeatures(globalFeatures, localFeatures)
51-
52-
private def combineFeatures(f1: ByteVector, f2: ByteVector): ByteVector = {
53-
if (f1.length == f2.length) {
54-
f1 | f2
55-
} else if (f1.length > f2.length) {
56-
f1 | f2.padLeft(f1.length)
57-
} else {
58-
f1.padLeft(f2.length) | f2
59-
}
60-
}
61-
}
48+
case class Init(features: ByteVector) extends SetupMessage
6249

6350
case class Error(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId {
6451
def toAscii: String = if (fr.acinq.eclair.isAsciiPrintable(data)) new String(data.toArray, StandardCharsets.US_ASCII) else "n/a"

eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzyPipe.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import scala.concurrent.duration._
2525
import scala.util.Random
2626

2727
/**
28-
* A Fuzzy [[fr.acinq.eclair.Pipe]] which randomly disconnects/reconnects peers.
29-
*/
28+
* A Fuzzy [[fr.acinq.eclair.Pipe]] which randomly disconnects/reconnects peers.
29+
*/
3030
class FuzzyPipe(fuzzy: Boolean) extends Actor with Stash with ActorLogging {
3131

3232
import scala.concurrent.ExecutionContext.Implicits.global
@@ -39,7 +39,7 @@ class FuzzyPipe(fuzzy: Boolean) extends Actor with Stash with ActorLogging {
3939
case _ => stash()
4040
}
4141

42-
def stayOrDisconnect(a: ActorRef, b: ActorRef, countdown: Int) = {
42+
def stayOrDisconnect(a: ActorRef, b: ActorRef, countdown: Int): Unit = {
4343
if (!fuzzy) context become connected(a, b, countdown - 1) // fuzzy mode disabled, we never disconnect
4444
else if (countdown > 1) context become connected(a, b, countdown - 1)
4545
else {
@@ -71,7 +71,7 @@ class FuzzyPipe(fuzzy: Boolean) extends Actor with Stash with ActorLogging {
7171
log.debug(f" X-${msg2String(msg)}%-6s--- B")
7272
case 'reconnect =>
7373
log.debug("RECONNECTED")
74-
val dummyInit = Init(ByteVector.empty, ByteVector.empty)
74+
val dummyInit = Init(ByteVector.empty)
7575
a ! INPUT_RECONNECTED(self, dummyInit, dummyInit)
7676
b ! INPUT_RECONNECTED(self, dummyInit, dummyInit)
7777
context become connected(a, b, Random.nextInt(40))

eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi
6767
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Alice.nodeParams, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA))
6868
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Bob.nodeParams, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB))
6969
within(30 seconds) {
70-
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
71-
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
70+
val aliceInit = Init(Alice.channelParams.features)
71+
val bobInit = Init(Bob.channelParams.features)
7272
registerA ! alice
7373
registerB ! bob
7474
// no announcements

eclair-core/src/test/scala/fr/acinq/eclair/channel/RecoverySpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ class RecoverySpec extends TestkitBaseClass with StateTestsHelperMethods {
3434
}
3535
}
3636

37-
def aliceInit = Init(ByteVector.empty, TestConstants.Alice.nodeParams.features)
37+
def aliceInit = Init(TestConstants.Alice.nodeParams.features)
3838

39-
def bobInit = Init(ByteVector.empty, TestConstants.Bob.nodeParams.features)
39+
def bobInit = Init(TestConstants.Bob.nodeParams.features)
4040

4141
test("use funding pubkeys from publish commitment to spend our output") { f =>
4242
import f._

0 commit comments

Comments
 (0)