Files
lnd/.gemini/styleguide.md
Elle Mouton c2baf6f30e .gemini: ignore char wrapping during code review
Gemini code review agent is bad at counting characters.
2025-09-01 10:16:44 +02:00

7.7 KiB

LND Style Guide

Code Documentation and Commenting

  • Always use the Golang code style described below in this document.
  • Readable code is the most important requirement for any commit created.
  • Comments must not explain the code 1:1 but instead explain the why behind a certain block of code, in case it requires contextual knowledge.
  • Unit tests must always use the require library. Either table driven unit tests or tests using the rapid library are preferred.
  • The line length MUST NOT exceed 80 characters, this is very important. You must count the Golang indentation (tabulator character) as 8 spaces when determining the line length. Use creative approaches or the wrapping rules specified below to make sure the line length isn't exceeded. HOWEVER: during code reviews, please leave this check up to the linter and do not comment on it (why? because gemini is bad at counting characters).
  • Every function must be commented with its purpose and assumptions.
  • Function comments must begin with the function name.
  • Function comments should be complete sentences.
  • Exported functions require detailed comments for the caller.

WRONG

// generates a revocation key
func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,
	revokePreimage []byte) *btcec.PublicKey {

RIGHT

// DeriveRevocationPubkey derives the revocation public key given the
// counterparty's commitment key, and revocation preimage derived via a
// pseudo-random-function. In the event that we (for some reason) broadcast a
// revoked commitment transaction, then if the other party knows the revocation
// preimage, then they'll be able to derive the corresponding private key to
// this private key by exploiting the homomorphism in the elliptic curve group.
//
// The derivation is performed as follows:
//
//   revokeKey := commitKey + revokePoint
//             := G*k + G*h
//             := G * (k+h)
//
// Therefore, once we divulge the revocation preimage, the remote peer is able
// to compute the proper private key for the revokeKey by computing:
//   revokePriv := commitPriv + revokePreimge mod N
//
// Where N is the order of the sub-group.
func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,
	revokePreimage []byte) *btcec.PublicKey {
  • In-body comments should explain the intention of the code.

WRONG

// return err if amt is less than 546
if amt < 546 {
	return err
}

RIGHT

// Treat transactions with amounts less than the amount which is considered dust
// as non-standard.
if amt < 546 {
	return err
}

Code Spacing and formatting

  • Segment code into logical stanzas separated by newlines.

WRONG

	witness := make([][]byte, 4)
	witness[0] = nil
	if bytes.Compare(pubA, pubB) == -1 {
		witness[1] = sigB
		witness[2] = sigA
	} else {
		witness[1] = sigA
		witness[2] = sigB
	}
	witness[3] = witnessScript
	return witness

RIGHT

	witness := make([][]byte, 4)

	// When spending a p2wsh multi-sig script, rather than an OP_0, we add
	// a nil stack element to eat the extra pop.
	witness[0] = nil

	// When initially generating the witnessScript, we sorted the serialized
	// public keys in descending order. So we do a quick comparison in order
	// to ensure the signatures appear on the Script Virtual Machine stack in
	// the correct order.
	if bytes.Compare(pubA, pubB) == -1 {
		witness[1] = sigB
		witness[2] = sigA
	} else {
		witness[1] = sigA
		witness[2] = sigB
	}

	// Finally, add the preimage as the last witness element.
	witness[3] = witnessScript

	return witness
  • Use spacing between case and select stanzas.

WRONG

	switch {
		case a:
			<code block>
		case b:
			<code block>
		case c:
			<code block>
		case d:
			<code block>
		default:
			<code block>
	}

RIGHT

	switch {
		// Brief comment detailing instances of this case (repeat below).
		case a:
			<code block>

		case b:
			<code block>

		case c:
			<code block>

		case d:
			<code block>

		default:
			<code block>
	}

Additional Style Constraints

80 character line length

  • Wrap columns at 80 characters.
  • Tabs are 8 spaces.

WRONG

myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e151fcee7"

RIGHT

myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e1" +
	"51fcee7"

Wrapping long function calls

  • If a function call exceeds the column limit, place the closing parenthesis on its own line and start all arguments on a new line after the opening parenthesis.

WRONG

value, err := bar(a,
	a, b, c)

RIGHT

value, err := bar(
	a, a, b, c,
)
  • Compact form is acceptable if visual symmetry of parentheses is preserved.

ACCEPTABLE

	response, err := node.AddInvoice(
		ctx, &lnrpc.Invoice{
			Memo:      "invoice",
			ValueMsat: int64(oneUnitMilliSat - 1),
		},
	)

PREFERRED

	response, err := node.AddInvoice(ctx, &lnrpc.Invoice{
		Memo:      "invoice",
		ValueMsat: int64(oneUnitMilliSat - 1),
	})

Exception for log and error message formatting

  • Minimize lines for log and error messages, while adhering to the 80-character limit.

WRONG

return fmt.Errorf(
	"this is a long error message with a couple (%d) place holders",
	len(things),
)

log.Debugf(
	"Something happened here that we need to log: %v",
	longVariableNameHere,
)

RIGHT

return fmt.Errorf("this is a long error message with a couple (%d) place "+
	"holders", len(things))

log.Debugf("Something happened here that we need to log: %v",
	longVariableNameHere)

Exceptions and additional styling for structured logging

  • Static messages: Use key-value pairs instead of formatted strings for the msg parameter.
  • Key-value attributes: Use slog.Attr helper functions.
  • Line wrapping: Structured log lines are an exception to the 80-character rule. Use one line per key-value pair for multiple attributes.

WRONG

log.DebugS(ctx, fmt.Sprintf("User %d just spent %.8f to open a channel", userID, 0.0154))

RIGHT

log.InfoS(ctx, "Channel open performed",
        slog.Int("user_id", userID),
        btclog.Fmt("amount", "%.8f", 0.00154))

Wrapping long function definitions

  • If function arguments exceed the 80-character limit, maintain indentation on following lines.
  • Do not end a line with an open parenthesis if the function definition is not finished.

WRONG

func foo(a, b, c,
) (d, error) {

func bar(a, b, c) (
	d, error,
) {

func baz(a, b, c) (
	d, error) {

RIGHT

func foo(a, b,
	c) (d, error) {

func baz(a, b, c) (d,
	error) {

func longFunctionName(
	a, b, c) (d, error) {
  • If a function declaration spans multiple lines, the body should start with an empty line.

WRONG

func foo(a, b, c,
	d, e) error {
	var a int
}

RIGHT

func foo(a, b, c,
	d, e) error {

	var a int
}

Use of Log Levels

  • Available levels: trace, debug, info, warn, error, critical.
  • Only use error for internal errors not triggered by external sources.

Testing

  • To run all tests for a specific package: make unit pkg=$pkg
  • To run a specific test case within a package: make unit pkg=$pkg case=$case

Git Commit Messages

  • Subject Line:

    • Format: subsystem: short description of changes
    • subsystem should be the package primarily affected (e.g., lnwallet, rpcserver).
    • For multiple packages, use + or , as a delimiter (e.g., lnwallet+htlcswitch).
    • For widespread changes, use multi:.
    • Keep it under 50 characters.
    • Use the present tense (e.g., "Fix bug", not "Fixed bug").
  • Message Body:

    • Separate from the subject with a blank line.
    • Explain the "what" and "why" of the change.
    • Wrap text to 72 characters.
    • Use bullet points for lists.