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

Add support for zero-conf and scid-alias #2224

Merged
merged 55 commits into from
Jun 15, 2022
Merged

Add support for zero-conf and scid-alias #2224

merged 55 commits into from
Jun 15, 2022

Conversation

pm47
Copy link
Member

@pm47 pm47 commented Apr 4, 2022

Builds on #2221 and #2269.

This implements lightning/bolts#910.

Commit-by-commit review strongly advised. 7e28c1a is a bit of a mouthful, but the other commits are mostly small.

Commit e7f7ef0 is optional, it skips blockchain validation for local channels, which would save a lot of bitcoind calls at startup for large nodes.

@pm47 pm47 force-pushed the zeroconf-scid-alias branch 2 times, most recently from 682d9cb to d091440 Compare April 11, 2022 15:57
@pm47 pm47 force-pushed the zeroconf-scid-alias branch 3 times, most recently from f21861b to aa2956f Compare May 17, 2022 16:59
case w: WatchFundingConfirmed if confirmations == 0 && w.minDepth == 0 =>
// if the tx doesn't have confirmations but we don't require any, we reply with a fake block index
// otherwise, we get the real short id
context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(BlockHeight(0), 0, tx))
Copy link
Member Author

Choose a reason for hiding this comment

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

Using a blockheight 0 for unconfirmed transactions is a bit hacky.

@@ -101,33 +101,46 @@ object ChannelTypes {
override def commitmentFormat: CommitmentFormat = UnsafeLegacyAnchorOutputsCommitmentFormat
override def toString: String = "anchor_outputs"
}
case object AnchorOutputsZeroFeeHtlcTx extends SupportedChannelType {
override def features: Set[InitFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)
case class AnchorOutputsZeroFeeHtlcTx(scidAlias: Boolean, zeroConf: Boolean) extends SupportedChannelType {
Copy link
Member Author

Choose a reason for hiding this comment

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

To reduce the cardinality of channel types, only AnchorOutputsZeroFeeHtlcTx channels can support scidAlias or zeroConf.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with that choice, it's a sensible one, but it means we won't be compatible with LDK (I believe they haven't shipped anchor outputs yet). But it's fine, we can wait for them to ship anchor outputs which should high be on their roadmap.

We do need to add tests to verify that we correctly reject channel types that use zero_conf or scid_alias with previous commitment types.

Comment on lines 314 to 299
* As funder we trust ourselves to not double spend funding txs: we could always use a zero-confirmation watch,
* but we need a scid to send the initial channel_update and remote may not provide an alias (and we don't want to
* trust the real scid sent by remote in their channel_ready). So we always wait for one conf, except if the channel
* has the zero-conf feature (because presumably the peer will sends an alias in that case).
Copy link
Member Author

@pm47 pm47 May 17, 2022

Choose a reason for hiding this comment

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

Note: a consequence of this is that the channel will only be zero-conf if the feature is set. It's not enough for the fundee to just trust us and send an early channel_ready because we will already have decided to wait for one conf (we would receive their alias in the channel_ready, but we put the watch before that in the channel fsm). I guess if we receive an early channel_ready with an alias, we can put a 2nd zero-conf watch, the first one will be ignored.

* @return number of confirmations needed
*/
def minDepthFundee(channelConf: ChannelConf, channelFeatures: ChannelFeatures, fundingSatoshis: Satoshi): Long = fundingSatoshis match {
case _ if channelFeatures.hasFeature(Features.ZeroConf) => 0 // zero-conf stay zero-conf, whatever the funding amount is
Copy link
Member Author

Choose a reason for hiding this comment

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

A bit reckless, even if option_zeroconf should only be enabled for a set of trusted peers.

@@ -59,6 +60,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
// we pass these to helpers classes so that they have the logging context
implicit def implicitLog: DiagnosticLoggingAdapter = diagLog

context.system.eventStream.subscribe(self, classOf[ShortChannelIdAssigned])
Copy link
Member Author

Choose a reason for hiding this comment

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

Not really happy with the router listening to yet another event, but I couldn't find a better way to fix the race condition (see bf39b07).

At least I was able to have ChannelRelayer stop listening anymore to that same event 🤷

("shortChannelId" | realshortchannelid) ::
("lastSent" | channelReadyCodec)).map {
case commitments :: shortChannelId :: lastSent :: HNil =>
DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId), localAlias = shortChannelId.toAlias, lastSent = lastSent)
Copy link
Member Author

Choose a reason for hiding this comment

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

Note how for backward compatibility we set both the realShortChannelId_opt and localAlias to the same value. It may not actually be the real scid on feature branches.

@@ -56,6 +65,8 @@ object ShortChannelId {
def outputIndex(shortChannelId: ShortChannelId): Int = (shortChannelId.id & 0xFFFF).toInt

def coordinates(shortChannelId: ShortChannelId): TxCoordinates = TxCoordinates(blockHeight(shortChannelId), txIndex(shortChannelId), outputIndex(shortChannelId))

def generateLocalAlias(): LocalAlias = new ShortChannelId(System.nanoTime()) with LocalAlias // TODO: fixme (duplicate, etc.)
Copy link
Member Author

Choose a reason for hiding this comment

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

⚠️ needs fixing, this is a dummy impl that leaks information on the creation date of the channel.

Since generating an alias only happens at channel creation, we can afford doing something "costly". For example this could be an AtomicLong that we initialize at startup by checking all currently assigned localAlias, and then we do an addAndGet with a random increment when we need to generate a new alias.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we really need a counter, wouldn't a randomLong() in the range that cannot be used by normal channels be enough?

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot afford a duplicate here though and the space isn't that big. We should expect a collision for 5 billion attempts on 64 bits and only 80k attempts for 32 bits right? https://en.wikipedia.org/wiki/Birthday_attack

Copy link
Member

Choose a reason for hiding this comment

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

It's true, the space isn't that big, but I'm afraid there may be privacy issues with generating those incrementally...I'll check what other implementations do

Copy link
Member

Choose a reason for hiding this comment

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

I did some maths to confirm this, let me know if my calculations look correct:

  • scids are 64 bits long
  • we want to reserve the range [0; 2^22] for real scids
  • that leaves us with 2^42 available values for aliases
  • we'll consider that we want to have 250k channels using aliases (~2^18)
  • the probability of a collision is then p(n) ~= 2^36 / 2^43 ~= 0.8%

This is indeed not great! But using a counter that we increment leaks information about the approximate creation time of the channel, which could let an attacker find the channel outpoint on-chain...so I believe that what we should do instead is:

  • generate an alias randomly in the [2^22:2^64] range
  • check our alias map to see if it's already assigned and re-roll if it is

It means we'll probably need to send a message to the router to obtain a new alias, the router is where we can guarantee uniqueness.

@pm47
Copy link
Member Author

pm47 commented May 20, 2022

Rebased

Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

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

This is a first pass on some details and high-level architecture, I'll dive into the actual usage of scid_alias in a second pass later, my brain wasn't able to handle it today :)

eclair-core/src/main/resources/reference.conf Show resolved Hide resolved
@@ -56,6 +65,8 @@ object ShortChannelId {
def outputIndex(shortChannelId: ShortChannelId): Int = (shortChannelId.id & 0xFFFF).toInt

def coordinates(shortChannelId: ShortChannelId): TxCoordinates = TxCoordinates(blockHeight(shortChannelId), txIndex(shortChannelId), outputIndex(shortChannelId))

def generateLocalAlias(): LocalAlias = new ShortChannelId(System.nanoTime()) with LocalAlias // TODO: fixme (duplicate, etc.)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we really need a counter, wouldn't a randomLong() in the range that cannot be used by normal channels be enough?

@pm47 pm47 force-pushed the zeroconf-scid-alias branch 2 times, most recently from 802ac3a to 9e1a675 Compare June 5, 2022 18:08
@codecov-commenter
Copy link

codecov-commenter commented Jun 5, 2022

Codecov Report

Merging #2224 (e9739d1) into master (4722755) will increase coverage by 0.16%.
The diff coverage is 95.79%.

@@            Coverage Diff             @@
##           master    #2224      +/-   ##
==========================================
+ Coverage   84.56%   84.73%   +0.16%     
==========================================
  Files         193      194       +1     
  Lines       14476    14624     +148     
  Branches      649      589      -60     
==========================================
+ Hits        12241    12391     +150     
+ Misses       2235     2233       -2     
Impacted Files Coverage Δ
...n/scala/fr/acinq/eclair/balance/BalanceActor.scala 10.16% <0.00%> (ø)
...ain/scala/fr/acinq/eclair/balance/Monitoring.scala 0.00% <0.00%> (ø)
.../acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala 88.13% <ø> (ø)
.../fr/acinq/eclair/blockchain/fee/FeeEstimator.scala 100.00% <ø> (ø)
...in/scala/fr/acinq/eclair/channel/Commitments.scala 94.76% <ø> (ø)
...cala/fr/acinq/eclair/crypto/TransportHandler.scala 90.21% <ø> (ø)
...main/scala/fr/acinq/eclair/db/DbEventHandler.scala 93.42% <ø> (ø)
.../main/scala/fr/acinq/eclair/db/DualDatabases.scala 10.94% <ø> (ø)
.../src/main/scala/fr/acinq/eclair/db/NetworkDb.scala 100.00% <ø> (ø)
...-core/src/main/scala/fr/acinq/eclair/package.scala 80.76% <ø> (ø)
... and 49 more

@pm47 pm47 force-pushed the zeroconf-scid-alias branch 3 times, most recently from fc7d4ee to ea388c5 Compare June 6, 2022 13:03
@pm47
Copy link
Member Author

pm47 commented Jun 6, 2022

Rebased.

@pm47 pm47 marked this pull request as ready for review June 6, 2022 14:44
@pm47
Copy link
Member Author

pm47 commented Jun 6, 2022

Follow up on #2269 (comment): I think it's better to defer to the dual-funding PR, as the problem still doesn't exist, and the present PR is already heavy enough.

Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

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

I've finished reviewing the code up to 957f014, but I haven't reviewed the tests yet.

Submitting this PR round and waiting for the changes to the scid types and #2310 and I'll move on to review the tests.

pm47 and others added 2 commits June 13, 2022 15:10
Instead of using a watch with minDepth=0, we can directly skip the
wait_for_funding_confirmed state when using 0-conf, which is less hacky.

Co-authored-by: pm47 <pm.padiou@gmail.com>
@t-bast t-bast mentioned this pull request Jun 14, 2022
Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

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

LGTM, I have some test improvements / fixes in #2314 and we should be good to go. I'll do some end-to-end tests on regtest with other implementations.

EDIT: we need to add release notes, can you do that @pm47?

Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

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

LGTM, we're almost ready to go!

assert(!getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf))
}

test("open a channel alice-bob (zero-conf disabled on both sides, requested via channel type by alice)") { f =>
Copy link
Member

Choose a reason for hiding this comment

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

In a follow-up PR, we should introduce a whitelist of nodes from which we accept 0-conf channels. If nodes are in that whitelist, we should accept the channel_type even though they didn't set the feature in their init.

We should maybe even go further than that and use 0-conf with those peers even if the channel_type doesn't include it. Either they'll accept it and send their channel_ready, or they won't and it's ok, we'll just wait for their channel_ready automatically.

docs/release-notes/eclair-vnext.md Outdated Show resolved Hide resolved
docs/release-notes/eclair-vnext.md Outdated Show resolved Hide resolved
t-bast
t-bast previously approved these changes Jun 15, 2022
Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

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

LGTM 🚀 ⚡

@pm47
Copy link
Member Author

pm47 commented Jun 15, 2022

🤟

@pm47 pm47 merged commit e5f5cd1 into master Jun 15, 2022
@pm47 pm47 deleted the zeroconf-scid-alias branch June 15, 2022 11:09
@pm47
Copy link
Member Author

pm47 commented Jun 15, 2022

Follow-ups to this PR include:

  • better way of generating aliases (see Add support for zero-conf and scid-alias #2224 (comment))
  • whitelist of zeroconf peers:
    • prevent users from accidentally enabling zeroconf for all peers
    • set min_depth=0 and send early channel_ready even if option_zeroconf bit isn't set in the channel type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants