Skip to content

Conversation

@thomash-acinq
Copy link
Member

@thomash-acinq thomash-acinq commented Nov 20, 2025

Add accountability signal for HTLCs, it replaces endorsement.
See lightning/bolts#1280

@thomash-acinq thomash-acinq force-pushed the accountability branch 4 times, most recently from c44e38f to 7e21640 Compare November 26, 2025 15:57
@thomash-acinq thomash-acinq force-pushed the accountability branch 6 times, most recently from f8160e5 to d807c65 Compare December 2, 2025 10:41
Add accountability signal for HTLCs, it replaces endorsement.
See lightning/bolts#1280
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 simpler and less invasive than the previous endorsement mechanism, I like it. Let's get that on master soon to avoid dealing with merge conflicts since a lot of tests are being updated (hopefully for the last time).

I think it's fine to set the accountability bit in the route blinding data even though it isn't in the spec yet, we can still change it on master in a follow-up PR if the final decision is slightly different than what we're doing.

case WrappedReputationScore(score) =>
new ChannelRelay(nodeParams, register, channels, r, upstream, score, context).start()
val accountable = r.add.accountable || incomingChannelOccupancy > 1 - nodeParams.relayParams.reservedBucket
if (accountable && !r.payload.upgradeAccountability) {
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

If we include this change, this isn't a "shadow deployment" anymore: we will reject payments that don't use the accountable TLV (existing nodes on the network) whenever the incoming channel is more than half full. We should only log this information for now but not reject HTLCs yet. I've fixed that in #3229.

val payment = if (useMultiPart) {
SendMultiPartPayment(payFsmAdapters, recipient, maxPaymentAttempts, routeParams)
val accountable = upstream.accountable || upstream.incomingChannelOccupancy > 1 - nodeParams.relayParams.reservedBucket
if (accountable && !upgradeAccountability) {
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

Same comment here about keeping it a "shadow deployment" for now (also fixed in #3229).

// @formatter:off
sealed trait Command
case class GetConfidence(replyTo: ActorRef[Reputation.Score], upstream: Upstream.Hot, downstream_opt: Option[PublicKey], fee: MilliSatoshi, currentBlockHeight: BlockHeight, expiry: CltvExpiry) extends Command
case class GetConfidence(replyTo: ActorRef[Reputation.Score], downstream_opt: Option[PublicKey], fee: MilliSatoshi, currentBlockHeight: BlockHeight, expiry: CltvExpiry, accountable: Boolean) extends Command
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

The downstream node_id is always known at that point, we don't need this to be an Option. I've removed the Option in #3229.

private val pathKey: Codec[PathKey] = (("length" | constant(hex"21")) :: ("pathKey" | publicKey)).as[PathKey]

case class Endorsement(level: Int) extends UpdateAddHtlcTlv
case object Accountable extends UpdateAddHtlcTlv
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

We've had issues in the past with case objects and scodec, I couldn't find the exact reason for it in the git history, but I think it's better to be consistent with other similar TLVs (e.g. RequireConfirmedInputs) and use a case class, which allows using the tlvField() helper codec and the TlvStream.get[T] helper method.

I've changed that in the first commit of #3229.

Comment on lines +227 to +229
sealed trait StandardPayload extends PerHopPayload {
def upgradeAccountability: Boolean = records.records.contains(UpgradeAccountability)
}
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

This isn't necessary, we can put this field directly in PerHopPayload and override it in BlindedPayload, which simplifies the trait hierarchy and reduces the diff with master. Removed in #3229.

assert(r.getConfidence(1000 msat, 0, BlockHeight(0), CltvExpiry(1), TimestampMilli(0) + 1.hour) < 0.000001)
}

test("multiple endorsement levels") {
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

We should remove the mention of endorsement, it's confusing since the spec only talk about accountability now. I've renamed those tests in #3229. They're a bit confusing because they get exactly the same result regardless of whether we set accountable = true or accountable = false, but that's because we're reusing the previous bucketing strategy that extends to more than just 2 buckets.

}
walletNodeId_opt match {
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) && !accountable => RelayNeedsFunding(walletNodeId, cmdFail)
Copy link
Member

@t-bast t-bast Dec 22, 2025

Choose a reason for hiding this comment

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

I'm not sure why you're adding the accountable check here when deciding whether to perform on-the-fly funding. Since this boolean can be true whenever the incoming channel is more than half-full, even though nodes aren't setting the accountability TLV, I don't think we should do this yet: we don't want to make payments fail that would have otherwise succeeded, we only want to collect data for now. I've removed this in #3229.

@t-bast t-bast marked this pull request as ready for review December 22, 2025 10:21
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.

3 participants