| 
 | 1 | +# tapd Style Guide  | 
 | 2 | + | 
 | 3 | +## Code Documentation and Commenting  | 
 | 4 | + | 
 | 5 | +- Always use the Golang code style described below in this document.   | 
 | 6 | +- Readable code is the most important requirement for any commit created.   | 
 | 7 | +- Comments must not explain the code 1:1 but instead explain the _why_ behind a   | 
 | 8 | +  certain block of code, in case it requires contextual knowledge.   | 
 | 9 | +- Unit tests must always use the `require` library. Either table driven unit   | 
 | 10 | +  tests or tests using the `rapid` library are preferred.   | 
 | 11 | +- The line length MUST NOT exceed 80 characters, this is very important.   | 
 | 12 | +  You must count the Golang indentation (tabulator character) as 8 spaces when   | 
 | 13 | +  determining the line length. Use creative approaches or the wrapping rules   | 
 | 14 | +  specified below to make sure the line length isn't exceeded. HOWEVER: during  | 
 | 15 | +  code reviews, please leave this check up to the linter and do not comment on  | 
 | 16 | +  it (why? because gemini is bad at counting characters).  | 
 | 17 | +- Every function must be commented with its purpose and assumptions.  | 
 | 18 | +- Function comments must begin with the function name.  | 
 | 19 | +- Function comments should be complete sentences.  | 
 | 20 | +- Exported functions require detailed comments for the caller.  | 
 | 21 | + | 
 | 22 | +**WRONG**  | 
 | 23 | +```go  | 
 | 24 | +// generates a revocation key  | 
 | 25 | +func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,  | 
 | 26 | +	revokePreimage []byte) *btcec.PublicKey {  | 
 | 27 | +```  | 
 | 28 | +**RIGHT**  | 
 | 29 | +```go  | 
 | 30 | +// DeriveRevocationPubkey derives the revocation public key given the  | 
 | 31 | +// counterparty's commitment key, and revocation preimage derived via a  | 
 | 32 | +// pseudo-random-function. In the event that we (for some reason) broadcast a  | 
 | 33 | +// revoked commitment transaction, then if the other party knows the revocation  | 
 | 34 | +// preimage, then they'll be able to derive the corresponding private key to  | 
 | 35 | +// this private key by exploiting the homomorphism in the elliptic curve group.  | 
 | 36 | +//  | 
 | 37 | +// The derivation is performed as follows:  | 
 | 38 | +//  | 
 | 39 | +//   revokeKey := commitKey + revokePoint  | 
 | 40 | +//             := G*k + G*h  | 
 | 41 | +//             := G * (k+h)  | 
 | 42 | +//  | 
 | 43 | +// Therefore, once we divulge the revocation preimage, the remote peer is able  | 
 | 44 | +// to compute the proper private key for the revokeKey by computing:  | 
 | 45 | +//   revokePriv := commitPriv + revokePreimge mod N  | 
 | 46 | +//  | 
 | 47 | +// Where N is the order of the sub-group.  | 
 | 48 | +func DeriveRevocationPubkey(commitPubKey *btcec.PublicKey,  | 
 | 49 | +	revokePreimage []byte) *btcec.PublicKey {  | 
 | 50 | +```  | 
 | 51 | +- In-body comments should explain the *intention* of the code.  | 
 | 52 | +
  | 
 | 53 | +**WRONG**  | 
 | 54 | +```go  | 
 | 55 | +// return err if amt is less than 546  | 
 | 56 | +if amt < 546 {  | 
 | 57 | +	return err  | 
 | 58 | +}  | 
 | 59 | +```  | 
 | 60 | +**RIGHT**  | 
 | 61 | +```go  | 
 | 62 | +// Treat transactions with amounts less than the amount which is considered dust  | 
 | 63 | +// as non-standard.  | 
 | 64 | +if amt < 546 {  | 
 | 65 | +	return err  | 
 | 66 | +}  | 
 | 67 | +```  | 
 | 68 | +
  | 
 | 69 | +## Code Spacing and formatting  | 
 | 70 | +
  | 
 | 71 | +- Segment code into logical stanzas separated by newlines.  | 
 | 72 | +
  | 
 | 73 | +**WRONG**  | 
 | 74 | +```go  | 
 | 75 | +	witness := make([][]byte, 4)  | 
 | 76 | +	witness[0] = nil  | 
 | 77 | +	if bytes.Compare(pubA, pubB) == -1 {  | 
 | 78 | +		witness[1] = sigB  | 
 | 79 | +		witness[2] = sigA  | 
 | 80 | +	} else {  | 
 | 81 | +		witness[1] = sigA  | 
 | 82 | +		witness[2] = sigB  | 
 | 83 | +	}  | 
 | 84 | +	witness[3] = witnessScript  | 
 | 85 | +	return witness  | 
 | 86 | +```  | 
 | 87 | +**RIGHT**  | 
 | 88 | +```go  | 
 | 89 | +	witness := make([][]byte, 4)  | 
 | 90 | + | 
 | 91 | +	// When spending a p2wsh multi-sig script, rather than an OP_0, we add  | 
 | 92 | +	// a nil stack element to eat the extra pop.  | 
 | 93 | +	witness[0] = nil  | 
 | 94 | + | 
 | 95 | +	// When initially generating the witnessScript, we sorted the serialized  | 
 | 96 | +	// public keys in descending order. So we do a quick comparison in order  | 
 | 97 | +	// to ensure the signatures appear on the Script Virtual Machine stack in  | 
 | 98 | +	// the correct order.  | 
 | 99 | +	if bytes.Compare(pubA, pubB) == -1 {  | 
 | 100 | +		witness[1] = sigB  | 
 | 101 | +		witness[2] = sigA  | 
 | 102 | +	} else {  | 
 | 103 | +		witness[1] = sigA  | 
 | 104 | +		witness[2] = sigB  | 
 | 105 | +	}  | 
 | 106 | + | 
 | 107 | +	// Finally, add the preimage as the last witness element.  | 
 | 108 | +	witness[3] = witnessScript  | 
 | 109 | + | 
 | 110 | +	return witness  | 
 | 111 | +```  | 
 | 112 | +
  | 
 | 113 | +- Use spacing between `case` and `select` stanzas.  | 
 | 114 | +
  | 
 | 115 | +**WRONG**  | 
 | 116 | +```go  | 
 | 117 | +	switch {  | 
 | 118 | +		case a:  | 
 | 119 | +			<code block>  | 
 | 120 | +		case b:  | 
 | 121 | +			<code block>  | 
 | 122 | +		case c:  | 
 | 123 | +			<code block>  | 
 | 124 | +		case d:  | 
 | 125 | +			<code block>  | 
 | 126 | +		default:  | 
 | 127 | +			<code block>  | 
 | 128 | +	}  | 
 | 129 | +```  | 
 | 130 | +**RIGHT**  | 
 | 131 | +```go  | 
 | 132 | +	switch {  | 
 | 133 | +		// Brief comment detailing instances of this case (repeat below).  | 
 | 134 | +		case a:  | 
 | 135 | +			<code block>  | 
 | 136 | + | 
 | 137 | +		case b:  | 
 | 138 | +			<code block>  | 
 | 139 | + | 
 | 140 | +		case c:  | 
 | 141 | +			<code block>  | 
 | 142 | + | 
 | 143 | +		case d:  | 
 | 144 | +			<code block>  | 
 | 145 | + | 
 | 146 | +		default:  | 
 | 147 | +			<code block>  | 
 | 148 | +	}  | 
 | 149 | +```  | 
 | 150 | +
  | 
 | 151 | +## Additional Style Constraints  | 
 | 152 | +
  | 
 | 153 | +### 80 character line length  | 
 | 154 | +
  | 
 | 155 | +- Wrap columns at 80 characters.  | 
 | 156 | +- Tabs are 8 spaces.  | 
 | 157 | +
  | 
 | 158 | +**WRONG**  | 
 | 159 | +```go  | 
 | 160 | +myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e151fcee7"  | 
 | 161 | +```  | 
 | 162 | +
  | 
 | 163 | +**RIGHT**  | 
 | 164 | +```go  | 
 | 165 | +myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e1" +  | 
 | 166 | +	"51fcee7"  | 
 | 167 | +```  | 
 | 168 | +
  | 
 | 169 | +### Wrapping long function calls  | 
 | 170 | +
  | 
 | 171 | +- If a function call exceeds the column limit, place the closing parenthesis  | 
 | 172 | +  on its own line and start all arguments on a new line after the opening  | 
 | 173 | +  parenthesis.  | 
 | 174 | +
  | 
 | 175 | +**WRONG**  | 
 | 176 | +```go  | 
 | 177 | +value, err := bar(a,  | 
 | 178 | +	a, b, c)  | 
 | 179 | +```  | 
 | 180 | +
  | 
 | 181 | +**RIGHT**  | 
 | 182 | +```go  | 
 | 183 | +value, err := bar(  | 
 | 184 | +	a, a, b, c,  | 
 | 185 | +)  | 
 | 186 | +```  | 
 | 187 | +
  | 
 | 188 | +- Compact form is acceptable if visual symmetry of parentheses is preserved.  | 
 | 189 | +
  | 
 | 190 | +**ACCEPTABLE**  | 
 | 191 | +```go  | 
 | 192 | +	response, err := node.AddInvoice(  | 
 | 193 | +		ctx, &lnrpc.Invoice{  | 
 | 194 | +			Memo:      "invoice",  | 
 | 195 | +			ValueMsat: int64(oneUnitMilliSat - 1),  | 
 | 196 | +		},  | 
 | 197 | +	)  | 
 | 198 | +```  | 
 | 199 | +
  | 
 | 200 | +**PREFERRED**  | 
 | 201 | +```go  | 
 | 202 | +	response, err := node.AddInvoice(ctx, &lnrpc.Invoice{  | 
 | 203 | +		Memo:      "invoice",  | 
 | 204 | +		ValueMsat: int64(oneUnitMilliSat - 1),  | 
 | 205 | +	})  | 
 | 206 | +```  | 
 | 207 | +
  | 
 | 208 | +### Exception for log and error message formatting  | 
 | 209 | +
  | 
 | 210 | +- Minimize lines for log and error messages, while adhering to the  | 
 | 211 | +  80-character limit.  | 
 | 212 | +
  | 
 | 213 | +**WRONG**  | 
 | 214 | +```go  | 
 | 215 | +return fmt.Errorf(  | 
 | 216 | +	"this is a long error message with a couple (%d) place holders",  | 
 | 217 | +	len(things),  | 
 | 218 | +)  | 
 | 219 | + | 
 | 220 | +log.Debugf(  | 
 | 221 | +	"Something happened here that we need to log: %v",  | 
 | 222 | +	longVariableNameHere,  | 
 | 223 | +)  | 
 | 224 | +```  | 
 | 225 | +
  | 
 | 226 | +**RIGHT**  | 
 | 227 | +```go  | 
 | 228 | +return fmt.Errorf("this is a long error message with a couple (%d) place "+  | 
 | 229 | +	"holders", len(things))  | 
 | 230 | + | 
 | 231 | +log.Debugf("Something happened here that we need to log: %v",  | 
 | 232 | +	longVariableNameHere)  | 
 | 233 | +```  | 
 | 234 | +
  | 
 | 235 | +### Exceptions and additional styling for structured logging  | 
 | 236 | +
  | 
 | 237 | +- **Static messages:** Use key-value pairs instead of formatted strings for the  | 
 | 238 | +  `msg` parameter.  | 
 | 239 | +- **Key-value attributes:** Use `slog.Attr` helper functions.  | 
 | 240 | +- **Line wrapping:** Structured log lines are an exception to the 80-character  | 
 | 241 | +  rule. Use one line per key-value pair for multiple attributes.  | 
 | 242 | +
  | 
 | 243 | +**WRONG**  | 
 | 244 | +```go  | 
 | 245 | +log.DebugS(ctx, fmt.Sprintf("User %d just spent %.8f to open a channel", userID, 0.0154))  | 
 | 246 | +```  | 
 | 247 | +
  | 
 | 248 | +**RIGHT**  | 
 | 249 | +```go  | 
 | 250 | +log.InfoS(ctx, "Channel open performed",  | 
 | 251 | +        slog.Int("user_id", userID),  | 
 | 252 | +        btclog.Fmt("amount", "%.8f", 0.00154))  | 
 | 253 | +```  | 
 | 254 | +
  | 
 | 255 | +### Wrapping long function definitions  | 
 | 256 | +
  | 
 | 257 | +- If function arguments exceed the 80-character limit, maintain indentation  | 
 | 258 | +  on following lines.  | 
 | 259 | +- Do not end a line with an open parenthesis if the function definition is not  | 
 | 260 | +  finished.  | 
 | 261 | +
  | 
 | 262 | +**WRONG**  | 
 | 263 | +```go  | 
 | 264 | +func foo(a, b, c,  | 
 | 265 | +) (d, error) {  | 
 | 266 | + | 
 | 267 | +func bar(a, b, c) (  | 
 | 268 | +	d, error,  | 
 | 269 | +) {  | 
 | 270 | + | 
 | 271 | +func baz(a, b, c) (  | 
 | 272 | +	d, error) {  | 
 | 273 | +```  | 
 | 274 | +**RIGHT**  | 
 | 275 | +```go  | 
 | 276 | +func foo(a, b,  | 
 | 277 | +	c) (d, error) {  | 
 | 278 | + | 
 | 279 | +func baz(a, b, c) (d,  | 
 | 280 | +	error) {  | 
 | 281 | + | 
 | 282 | +func longFunctionName(  | 
 | 283 | +	a, b, c) (d, error) {  | 
 | 284 | +```  | 
 | 285 | +
  | 
 | 286 | +- If a function declaration spans multiple lines, the body should start with an  | 
 | 287 | +  empty line.  | 
 | 288 | +
  | 
 | 289 | +**WRONG**  | 
 | 290 | +```go  | 
 | 291 | +func foo(a, b, c,  | 
 | 292 | +	d, e) error {  | 
 | 293 | +	var a int  | 
 | 294 | +}  | 
 | 295 | +```  | 
 | 296 | +**RIGHT**  | 
 | 297 | +```go  | 
 | 298 | +func foo(a, b, c,  | 
 | 299 | +	d, e) error {  | 
 | 300 | + | 
 | 301 | +	var a int  | 
 | 302 | +}  | 
 | 303 | +```  | 
 | 304 | +
  | 
 | 305 | +## Use of Log Levels  | 
 | 306 | +
  | 
 | 307 | +- Available levels: `trace`, `debug`, `info`, `warn`, `error`, `critical`.  | 
 | 308 | +- Only use `error` for internal errors not triggered by external sources.  | 
 | 309 | +
  | 
 | 310 | +## Testing  | 
 | 311 | +
  | 
 | 312 | +- To run all tests for a specific package:  | 
 | 313 | +  `make unit pkg=$pkg`  | 
 | 314 | +- To run a specific test case within a package:  | 
 | 315 | +  `make unit pkg=$pkg case=$case`  | 
 | 316 | +
  | 
 | 317 | +## Git Commit Messages  | 
 | 318 | +
  | 
 | 319 | +- **Subject Line:**  | 
 | 320 | +  - Format: `subsystem: short description of changes`  | 
 | 321 | +  - `subsystem` should be the package primarily affected (e.g., `lnwallet`, `rpcserver`).  | 
 | 322 | +  - For multiple packages, use `+` or `,` as a delimiter (e.g., `lnwallet+htlcswitch`).  | 
 | 323 | +  - For widespread changes, use `multi:`.  | 
 | 324 | +  - Keep it under 50 characters.  | 
 | 325 | +  - Use the present tense (e.g., "Fix bug", not "Fixed bug").  | 
 | 326 | +
  | 
 | 327 | +- **Message Body:**  | 
 | 328 | +  - Separate from the subject with a blank line.  | 
 | 329 | +  - Explain the "what" and "why" of the change.  | 
 | 330 | +  - Wrap text to 72 characters.  | 
 | 331 | +  - Use bullet points for lists.  | 
0 commit comments