Separate internal channel config from features#1852
Separate internal channel config from features#1852t-bast merged 3 commits intoonly-store-remote-sigsfrom
Conversation
|
Did you consider modifying |
Yes I did, but I then decided to separate them. In most cases they are really unrelated and you only need one of them. The only benefit to keep them encapsulated in a |
|
Ok. One of my concerns was that to implement custom extensions we would need both fields, and that it may even be true for parameter validation, but overall this PR LGTM. |
|
IMO most custom extensions behave like channel features, so they should use only the channel type part, not the channel internal config. |
sstone
left a comment
There was a problem hiding this comment.
I've rebased #1846 on top of this (see https://github.com/ACINQ/eclair/tree/implement-upfront-shutdown-script-channel-version-migration) and everything LGTM.
pm47
left a comment
There was a problem hiding this comment.
The clash between ChannelType and ChannelTypes is unfortunate. I would rename (maybe in a separate PR):
State->ChannelStateData->ChannelDataChannelTypes.scala->ChannelData.scala
eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfigOptions.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelType.scala
Outdated
Show resolved
Hide resolved
eclair-node/src/main/scala/fr/acinq/eclair/api/serde/JsonSerializers.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelType.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelType.scala
Outdated
Show resolved
Hide resolved
I agree, it's probably better to do it in a separate PR that only does that and no logic change. |
eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelConfigOptions.scala
Outdated
Show resolved
Hide resolved
0adaeaa to
25a983e
Compare
Our current ChannelVersion field mixes two unrelated concepts: channel features (as defined in Bolt 9) and channel internals (such as custom key derivation). It is more future-proof to separate these two unrelated concepts and will make it easier to implement channel types (see lightning/bolts#880).
This will be necessary when we implement `upfront_shutdown_script`. This field can be set to `None` for all current channels as we don't support the feature yet.
25a983e to
0b520dd
Compare
| * Internal configuration option impacting the channel's structure or behavior. | ||
| * This must be set when creating the channel and cannot be changed afterwards. | ||
| */ | ||
| trait ChannelConfigOption { |
There was a problem hiding this comment.
Nit:
| trait ChannelConfigOption { | |
| trait ChannelConfig { |
or
| trait ChannelConfigOption { | |
| trait ChannelOptions { |
There was a problem hiding this comment.
Why? It's really a configuration option...it's not a complete configuration so ChannelConfig feels weird IMO, and ChannelOption is on the contrary a bit too vague, isn't it?
There was a problem hiding this comment.
It's just that everywhere we are using: channelConfig: ChannelConfigOptions
There was a problem hiding this comment.
But it's different, it's because it's a ChannelConfigOptions, which is a collection of ChannelConfigOption.
I agree this one could be renamed ChannelConfigOptions -> ChannelConfig, but not the trait on individual options.
|
|
||
| object ChannelConfigOptions { | ||
|
|
||
| def standard: ChannelConfigOptions = ChannelConfigOptions(activated = Set(FundingPubKeyBasedChannelKeyPath)) |
There was a problem hiding this comment.
Why not a val here?
| def standard: ChannelConfigOptions = ChannelConfigOptions(activated = Set(FundingPubKeyBasedChannelKeyPath)) | |
| val standard: ChannelConfigOptions = ChannelConfigOptions(activated = Set(FundingPubKeyBasedChannelKeyPath)) |
There was a problem hiding this comment.
No reason, this should be a val indeed.
eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
Show resolved
Hide resolved
| (("remoteParams" | remoteParamsCodec) :: | ||
| ("channelFlags" | byte) :: | ||
| ("localCommit" | localCommitCodec(remoteParams.fundingPubKey)) :: | ||
| ("remoteCommit" | remoteCommitCodec) :: | ||
| ("localChanges" | localChangesCodec) :: | ||
| ("remoteChanges" | remoteChangesCodec) :: | ||
| ("localNextHtlcId" | uint64overflow) :: | ||
| ("remoteNextHtlcId" | uint64overflow) :: | ||
| ("originChannels" | originsMapCodec) :: | ||
| ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: | ||
| ("commitInput" | inputInfoCodec) :: | ||
| ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: | ||
| ("channelId" | bytes32) | ||
| }) | ||
| }).as[Commitments].decodeOnly | ||
| ("localCommit" | localCommitCodec) :: | ||
| ("remoteCommit" | remoteCommitCodec) :: | ||
| ("localChanges" | localChangesCodec) :: | ||
| ("remoteChanges" | remoteChangesCodec) :: | ||
| ("localNextHtlcId" | uint64overflow) :: | ||
| ("remoteNextHtlcId" | uint64overflow) :: | ||
| ("originChannels" | originsMapCodec) :: | ||
| ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: | ||
| ("commitInput" | inputInfoCodec) :: | ||
| ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: | ||
| ("channelId" | bytes32)) | ||
| }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly |
There was a problem hiding this comment.
The outermost parentheses can be removed (same in other file).
There was a problem hiding this comment.
I noticed that when comparing both PRs to master
| val channelConfigCodec: Codec[ChannelConfigOptions] = lengthDelimited(bytes).xmap(b => { | ||
| val activated: Set[ChannelConfigOption] = b.bits.toIndexedSeq.reverse.zipWithIndex.collect { | ||
| case (true, 0) => ChannelConfigOptions.FundingPubKeyBasedChannelKeyPath | ||
| }.toSet | ||
| ChannelConfigOptions(activated) | ||
| }, cfg => { | ||
| val indices = cfg.activated.map(_.supportBit) | ||
| if (indices.isEmpty) { | ||
| ByteVector.empty | ||
| } else { | ||
| // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits. | ||
| var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits | ||
| indices.foreach(i => buffer = buffer.set(i)) | ||
| buffer.reverse.bytes | ||
| } | ||
| }) |
There was a problem hiding this comment.
This codec is a bit 🤢, but unfortunately it seems scodec doesn't work well with bitfields.
Since we use bitfields at several places, it's probably worth it to he define a generic byte-aligned, right-to-left, bitfield codec? I'll give it a try.
There was a problem hiding this comment.
Agreed, it's useful so that we don't unintentionally go back to storing something that isn't byte-aligned!
| val indices = cfg.options.map(_.supportBit) | ||
| if (indices.isEmpty) { | ||
| ByteVector.empty | ||
| } else { | ||
| // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits. | ||
| var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits | ||
| indices.foreach(i => buffer = buffer.set(i)) | ||
| buffer.reverse.bytes | ||
| } |
There was a problem hiding this comment.
| val indices = cfg.options.map(_.supportBit) | |
| if (indices.isEmpty) { | |
| ByteVector.empty | |
| } else { | |
| // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits. | |
| var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits | |
| indices.foreach(i => buffer = buffer.set(i)) | |
| buffer.reverse.bytes | |
| } | |
| val indices = cfg.activated.map(_.supportBit) | |
| val maxLength = indices.maxOption.getOrElse(-1) + 1 | |
| val buffer = indices.foldLeft(BitVector.low(maxLength)) { | |
| case (bitfield, i) => bitfield.set(i) | |
| } | |
| buffer.bytes.bits.reverse.bytes | |
| } |
| htlcBasepoint: PublicKey, | ||
| features: Features) | ||
| features: Features, | ||
| shutdownScript: Option[ByteVector]) |
There was a problem hiding this comment.
The naming is inconsistent LocalParams.defaultFinalScriptPubKey and RemoteParams.shutdownScript
Either:
LocalParams.defaultFinalScriptPubKeyandRemoteParams.upfrontFinalScriptPubKey
orLocalParams.defaultShutdownScriptandRemoteParams.upfrontShutdownScript
I would keep the upfront in any case.
There was a problem hiding this comment.
Let's revisit the naming afterwards.
* Separate internal channel config from features Our current ChannelVersion field mixes two unrelated concepts: channel features (as defined in Bolt 9) and channel internals (such as custom key derivation). It is more future-proof to separate these two unrelated concepts and will make it easier to implement channel types (see lightning/bolts#880). * Add shutdown script to channel remote params This will be necessary when we implement `upfront_shutdown_script`. This field can be set to `None` for all current channels as we don't support the feature yet.
* Separate internal channel config from features Our current ChannelVersion field mixes two unrelated concepts: channel features (as defined in Bolt 9) and channel internals (such as custom key derivation). It is more future-proof to separate these two unrelated concepts and will make it easier to implement channel types (see lightning/bolts#880). * Add shutdown script to channel remote params This will be necessary when we implement `upfront_shutdown_script`. This field can be set to `None` for all current channels as we don't support the feature yet.
Our current ChannelVersion field mixes two unrelated concepts: channel features (as defined in Bolt 9) and channel internals (such as custom key derivation). It is more future-proof to separate these two unrelated concepts and will make it easier to implement channel types (see lightning/bolts#880).
The second commit adds an optional shutdown script to the channel's remote params, which will let us support
upfront_shutdown_scriptlater.NB: this is PR to the
only-store-remote-sigsbranch, to ensure we squash all channel codec changes into a single commit onmaster.