Skip to content

lnrpc: fix contradictory unit descriptions for fee_per_mil and fee_rate - #11155

Open
freeport-porter wants to merge 1 commit into
lightningnetwork:masterfrom
freeport-porter:porter/fix-fee-unit-docs
Open

lnrpc: fix contradictory unit descriptions for fee_per_mil and fee_rate#11155
freeport-porter wants to merge 1 commit into
lightningnetwork:masterfrom
freeport-porter:porter/fix-fee-unit-docs

Conversation

@freeport-porter

Copy link
Copy Markdown

Change Description

Fixes #4155.

The comments for fee_per_mil and fee_rate in ChannelFeeReport describe units that cannot both be right. fee_per_mil is documented as an absolute amount — "the amount charged per milli-satoshis transferred expressed in millionths of a satoshi" — while fee_rate is documented as "the effective fee rate in milli-satoshis... computed by dividing the fee_per_mil value by 1 million". An absolute amount divided by a million is not a rate, and a rate is not itself denominated in milli-satoshis.

Reading the code: fee_per_mil is set from edgePolicy.FeeProportionalMillionths and fee_rate is that value divided by feeBase = 1000000 (rpcserver.go). The proportional fee is applied as rate * amt_msat / 1000000 (htlcswitch/link.go ExpectedFee, and ComputeFee in graph/db/models). So the field is a parts-per-million share of the amount forwarded — a dimensionless ratio, not an amount — and fee_rate is the same ratio written as a plain fraction. The values returned by lncli feereport were always correct; only the prose was wrong.

The identical wording had since been copied onto inbound_fee_per_mil, which is populated from the equivalent inbound rate (CalcFee in graph/db/models/inbound_fee.go, also over 1e6), so this corrects that too.

On the wording — this part is a proposal, please pick

The thread did not settle on a framing and I do not want to pre-empt that. @yyforyongyu wrote:

The first is to fix the inaccuracies in the documentations, which is what this issue is about.

and derived the rate as nano-satoshis per milli-satoshi. @jhoenicke argued the opposite:

I think stating it in nanosat per millisat confuses people even more. It's a fraction. Just say its in "per million"

Both are dimensionally valid; they differ only in presentation. I went with the plain per-million framing because it is already how this same quantity is described elsewhere in this file — PolicyUpdateRequest.fee_rate_ppm is "parts per million" — and in lncli updatechanpolicy. Consistency with existing wording seemed the least surprising choice for a docs fix, but I am happy to switch to the nanosat/millisat framing, or to any wording a maintainer prefers. Just say which and I will push it.

I deliberately did not act on two adjacent suggestions from the thread, as both are larger than a documentation fix and want their own discussion:

  • Renaming fee_per_mil (@jhoenicke notes it can be misread as "per mille", i.e. per thousand). That is a breaking API change. The new comment says "parts per million" explicitly, which resolves the ambiguity in prose without touching the wire API.
  • Introducing a lnwire.NanoSatoshi type (@yyforyongyu's second suggestion).

Steps to Test

No behaviour change, so there is nothing to test at runtime. To verify the generated artefacts match the proto:

make rpc-check

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included. — n/a, documentation only.
  • Bug fixes contain tests triggering the bug to prevent regressions. — n/a, no code change.

Code Style and Documentation

  • The change is not insubstantial. This is a wrong-unit description in the public RPC API that caused a user to file this issue after lncli feereport and lnd -h appeared to contradict each other. The issue was triaged and labelled documentation by a maintainer.
  • The change obeys the Code Documentation and Commenting guidelines, and lines wrap at 80.
  • Commits follow the Ideal Git Commit Structure.
  • Any new logging statements use an appropriate subsystem and logging level. — n/a.
  • Any new lncli commands have appropriate tags in the comments for the rpc in the proto file. — n/a.
  • There is a change description in the release notes.

The comments for `fee_per_mil` and `fee_rate` in `ChannelFeeReport`
describe incompatible units. `fee_per_mil` is documented as an absolute
amount -- "the amount charged per milli-satoshis transferred expressed in
millionths of a satoshi" -- while `fee_rate` is documented as "the
effective fee rate in milli-satoshis... computed by dividing the
fee_per_mil value by 1 million". An absolute amount divided by a million
does not yield a rate, and a rate is not itself denominated in
milli-satoshis, so the two descriptions cannot both be right.

Reading the code that populates the message, `fee_per_mil` is set from
`edgePolicy.FeeProportionalMillionths` and `fee_rate` is that value
divided by `feeBase = 1000000` (rpcserver.go). The proportional fee is
applied as `rate * amt_msat / 1000000` (htlcswitch/link.go
`ExpectedFee`, graph/db/models `ComputeFee`), so the field is a
parts-per-million share of the amount forwarded: a dimensionless ratio,
not an amount. `fee_rate` is the same ratio as a plain fraction.

Document both fields accordingly, and apply the same correction to
`inbound_fee_per_mil`, which duplicated the original wording verbatim
and is populated from the equivalent inbound rate
(graph/db/models/inbound_fee.go `CalcFee`, also over 1e6).

The chosen "parts per million" framing matches how the same quantity is
already described elsewhere in this file (`PolicyUpdateRequest.
fee_rate_ppm`) and in `lncli updatechanpolicy`.

Documentation only: no field renames, no reordering, no behaviour or
wire-format change. Generated stubs and the swagger definition are
regenerated from the updated proto.

Fixes lightningnetwork#4155
@freeport-porter
freeport-porter force-pushed the porter/fix-fee-unit-docs branch from 42835cc to d9d0010 Compare September 1, 2026 14:06
@github-actions github-actions Bot added the severity-high Requires knowledgeable engineer review label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🟠 PR Severity: HIGH

file path classification | 4 files | 41 lines changed

🟠 High (3 files)
  • lnrpc/lightning.proto - RPC/API definition change (field comments for ChannelFeeReport)
  • lnrpc/lightning.pb.go - generated from the .proto change above
  • lnrpc/lightning.swagger.json - generated API docs reflecting the .proto change
🟢 Low (1 file)
  • docs/release-notes/release-notes-0.22.0.md - release notes entry

Analysis

This PR only edits field-comment text in lnrpc/lightning.proto (and the corresponding generated .pb.go and swagger artifacts) to fix contradictory unit documentation for fee_per_mil, fee_rate, and inbound_fee_per_mil in ChannelFeeReport. No wire format, field numbers, or RPC behavior change — this is a documentation-only fix.

Severity is HIGH because the touched files fall under lnrpc/* (RPC/API definitions), which requires review from someone familiar with the public API surface to confirm no behavioral or semantic changes slipped in alongside the comment fix. No bump conditions were met (file count, line count, and critical-package thresholds are all well under the bump triggers).


To override, add a severity-override-{critical,high,medium,low} label.

@Lrifton92 Lrifton92 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Checked the claim against the code rather than the thread. ChannelFeeReport.fee_per_mil is populated from the edge policy's FeeProportionalMillionths (feeRateFixedPoint in rpcserver.go), and that value is applied as BaseFee + amt * rate / 1000000 in htlcswitch.ExpectedFee and ChannelEdgePolicy.ComputeFee; the inbound rate goes through InboundFee.CalcFee with the same feeRateParts = 1e6 divisor. So "N milli-satoshis for every 1,000,000 milli-satoshis forwarded" is exactly what the code does, and fee_rate = fee_per_mil / 1e6 is that ratio written as a fraction. The old prose was wrong in both directions, as described.

The three artefacts move together: lightning.proto, the lightning.pb.go struct comments and the lightning.swagger.json descriptions carry the same wording, and only comment lines change in the generated code, so there is no wire or behaviour impact. The wording matches how the same quantity is already described on fee_rate_ppm in this file ("parts per million"), which seems the right default for a docs-only fix; the nanosat/millisat framing can still be settled in the issue without blocking this.

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

Labels

severity-high Requires knowledgeable engineer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

confusing unit descriptions for "fee_per_mil" and "fee_rate"

2 participants