Skip to content

Commit 4883620

Browse files
authored
Merge pull request #543 from synonymdev/feat/logs-polish
feat: Logs polish
2 parents 7093157 + 12f5fe6 commit 4883620

File tree

10 files changed

+145
-135
lines changed

10 files changed

+145
-135
lines changed

.github/workflows/claude-code-review.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ jobs:
5151
5252
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
5353
54-
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
55-
5654
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
5755
# or https://code.claude.com/docs/en/cli-reference for available options
58-
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
56+
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
192192
- ALWAYS be mindful of thread safety when working with mutable lists & state
193193
- ALWAYS split screen composables into parent accepting viewmodel + inner private child accepting state and callbacks `Content()`
194194
- ALWAYS name lambda parameters in a composable function using present tense, NEVER use past tense
195-
- ALWAYS list 3 suggested commit messages after implementation work
195+
- ALWAYS list 3 suggested commit messages after implementation work for the entire set of uncommitted changes
196196
- NEVER use `wheneverBlocking` in unit test expression body functions wrapped in a `= test {}` lambda
197197
- ALWAYS wrap unit tests `setUp` methods mocking suspending calls with `runBlocking`, e.g `setUp() = runBlocking { }`
198198
- ALWAYS add business logic to Repository layer via methods returning `Result<T>` and use it in ViewModels

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ android {
4545
applicationId = "to.bitkit"
4646
minSdk = 28
4747
targetSdk = 36
48-
versionCode = 16
49-
versionName = "0.0.16"
48+
versionCode = 17
49+
versionName = "0.0.17"
5050
testInstrumentationRunner = "to.bitkit.test.HiltTestRunner"
5151
vectorDrawables {
5252
useSupportLibrary = true

app/detekt-baseline.xml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@
3333
<ID>EnumNaming:BlocktankNotificationType.kt$BlocktankNotificationType$orderPaymentConfirmed</ID>
3434
<ID>EnumNaming:BlocktankNotificationType.kt$BlocktankNotificationType$wakeToTimeout</ID>
3535
<ID>ForbiddenComment:ActivityDetailScreen.kt$/* TODO: Implement assign functionality */</ID>
36-
<ID>ForbiddenComment:ActivityRow.kt$// TODO: calculate confirmsIn text</ID>
3736
<ID>ForbiddenComment:BoostTransactionViewModel.kt$BoostTransactionUiState$// TODO: Implement dynamic time estimation</ID>
3837
<ID>ForbiddenComment:ContentView.kt$// TODO: display as sheet</ID>
39-
<ID>ForbiddenComment:Env.kt$Env$// TODO: remove this to load from BT API instead</ID>
4038
<ID>ForbiddenComment:ExternalNodeViewModel.kt$ExternalNodeViewModel$// TODO: pass customFeeRate to ldk-node when supported</ID>
4139
<ID>ForbiddenComment:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$// TODO: sort channels to get consistent index; node.listChannels returns a list in random order</ID>
4240
<ID>ForbiddenComment:LightningService.kt$LightningService$// TODO: cleanup sensitive data after implementing a `SecureString` value holder for Keychain return values</ID>
@@ -60,15 +58,12 @@
6058
<ID>LongParameterList:CoreService.kt$OnchainService$( mnemonicPhrase: String, derivationPathStr: String?, network: Network?, bip39Passphrase: String?, isChange: Boolean?, startIndex: UInt?, count: UInt?, )</ID>
6159
<ID>LongParameterList:WidgetsRepo.kt$WidgetsRepo$( @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val newsService: NewsService, private val factsService: FactsService, private val blocksService: BlocksService, private val weatherService: WeatherService, private val priceService: PriceService, private val widgetsStore: WidgetsStore, private val settingsStore: SettingsStore, )</ID>
6260
<ID>LoopWithTooManyJumpStatements:MonetaryVisualTransformation.kt$MonetaryVisualTransformation.&lt;no name provided&gt;$for</ID>
63-
<ID>MagicNumber:ActivityDetailScreen.kt$40</ID>
64-
<ID>MagicNumber:ActivityExploreScreen.kt$40</ID>
6561
<ID>MagicNumber:ActivityListViewModel.kt$ActivityListViewModel$300</ID>
6662
<ID>MagicNumber:AddressViewerScreen.kt$1500000L</ID>
6763
<ID>MagicNumber:AddressViewerScreen.kt$250000L</ID>
6864
<ID>MagicNumber:AddressViewerViewModel.kt$AddressViewerViewModel$300</ID>
6965
<ID>MagicNumber:AppStatus.kt$0.4f</ID>
7066
<ID>MagicNumber:ArticleModel.kt$24</ID>
71-
<ID>MagicNumber:ArticleModel.kt$30</ID>
7267
<ID>MagicNumber:ArticleModel.kt$60</ID>
7368
<ID>MagicNumber:BackupNavSheetViewModel.kt$BackupNavSheetViewModel$200</ID>
7469
<ID>MagicNumber:ChannelDetailScreen.kt$1.5f</ID>
@@ -97,7 +92,6 @@
9792
<ID>MagicNumber:ShowMnemonicScreen.kt$12</ID>
9893
<ID>MagicNumber:ShowMnemonicScreen.kt$24</ID>
9994
<ID>MagicNumber:ShowMnemonicScreen.kt$300</ID>
100-
<ID>MagicNumber:Slider.kt$20</ID>
10195
<ID>MagicNumber:SpendingConfirmScreen.kt$300</ID>
10296
<ID>MagicNumber:SwipeToConfirm.kt$1500</ID>
10397
<ID>MatchingDeclarationName:AddressType.kt$AddressTypeInfo</ID>
@@ -118,7 +112,6 @@
118112
<ID>MaxLineLength:HeadlineCard.kt$headline = "How Bitcoin changed El Salvador in more ways a big headline to test the text overflooooooow"</ID>
119113
<ID>MaxLineLength:LightningBalance.kt$is LightningBalance.ClaimableAwaitingConfirmations -&gt; "Claimable Awaiting Confirmations (Height: $confirmationHeight)"</ID>
120114
<ID>MaxLineLength:LightningConnectionsScreen.kt$if (showClosed) R.string.lightning__conn_closed_hide else R.string.lightning__conn_closed_show</ID>
121-
<ID>MaxLineLength:LightningRepo.kt$LightningRepo$"Cannot execute $operationName: Node is ${_lightningState.value.nodeLifecycleState} and not starting"</ID>
122115
<ID>MaxLineLength:LightningRepo.kt$LightningRepo$"accelerateByCpfp error originalTxId: $originalTxId, satsPerVByte: $satsPerVByte destinationAddress: $destinationAddress"</ID>
123116
<ID>MaxLineLength:LightningRepo.kt$LightningRepo$"accelerateByCpfp success, newDestinationTxId: $newDestinationTxId originalTxId: $originalTxId, satsPerVByte: $satsPerVByte destinationAddress: $destinationAddress"</ID>
124117
<ID>MaxLineLength:LightningRepo.kt$LightningRepo$"bumpFeeByRbf success, replacementTxId: $replacementTxId originalTxId: $originalTxId, satsPerVByte: $satsPerVByte"</ID>
@@ -144,14 +137,11 @@
144137
<ID>MaximumLineLength:SettingsScreen.kt$ </ID>
145138
<ID>MaximumLineLength:WeatherService.kt$WeatherService$ </ID>
146139
<ID>MayBeConst:Env.kt$Env$val walletSyncIntervalSecs = 10_uL // TODO review</ID>
147-
<ID>MayBeConst:Env.kt$Env.TransactionDefaults$/** * Minimum value in sats for an output. Outputs below the dust limit may not be processed because the fees * required to include them in a block would be greater than the value of the transaction itself. * */ val dustLimit = 546u</ID>
148-
<ID>MayBeConst:Env.kt$Env.TransactionDefaults$/** Total recommended tx base fee in sats */ val recommendedBaseFee = 256u</ID>
149140
<ID>MemberNameEqualsClassName:Keychain.kt$Keychain$private val keychain = context.keychainDataStore</ID>
150141
<ID>NestedBlockDepth:Context.kt$fun Context.copyAssetToStorage(asset: String, dest: String)</ID>
151142
<ID>NestedBlockDepth:LogsRepo.kt$LogsRepo$private fun createZipBase64(logFiles: List&lt;LogFile&gt;): String</ID>
152143
<ID>NestedBlockDepth:MonetaryVisualTransformation.kt$MonetaryVisualTransformation$private fun createOffsetMapping(original: String, transformed: String): OffsetMapping</ID>
153144
<ID>NestedBlockDepth:ShopWebViewInterface.kt$ShopWebViewInterface$@JavascriptInterface fun postMessage(message: String)</ID>
154-
<ID>NoUnusedImports:ActivityDetailScreen.kt$to.bitkit.ui.screens.wallets.activity.ActivityDetailScreen.kt</ID>
155145
<ID>NoWildcardImports:LightningChannel.kt$import androidx.compose.foundation.layout.*</ID>
156146
<ID>PrintStackTrace:ShareSheet.kt$e</ID>
157147
<ID>ReturnCount:AppViewModel.kt$AppViewModel$private suspend fun handleSanityChecks(amountSats: ULong)</ID>

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import to.bitkit.repositories.LightningRepo
3737
import to.bitkit.services.CoreService
3838
import to.bitkit.ui.pushNotification
3939
import to.bitkit.utils.Logger
40-
import to.bitkit.utils.withPerformanceLogging
40+
import to.bitkit.utils.measured
4141
import kotlin.time.Duration.Companion.minutes
4242

4343
@Suppress("LongParameterList")
@@ -74,7 +74,7 @@ class WakeNodeWorker @AssistedInject constructor(
7474
Logger.debug("${this::class.simpleName} notification payload: $notificationPayload")
7575

7676
try {
77-
withPerformanceLogging {
77+
measured(TAG) {
7878
lightningRepo.start(
7979
walletIndex = 0,
8080
timeout = timeout,
@@ -252,4 +252,8 @@ class WakeNodeWorker @AssistedInject constructor(
252252

253253
deliverSignal.complete(Unit)
254254
}
255+
256+
companion object {
257+
private const val TAG = "WakeNodeWorker"
258+
}
255259
}

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 60 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -119,28 +119,27 @@ class LightningRepo @Inject constructor(
119119
): Result<T> = withContext(bgDispatcher) {
120120
Logger.verbose("Operation called: $operationName", context = TAG)
121121

122-
if (_lightningState.value.nodeLifecycleState.isRunning()) {
122+
val nodeLifecycleState = _lightningState.value.nodeLifecycleState
123+
if (nodeLifecycleState.isRunning()) {
123124
return@withContext executeOperation(operationName, operation)
124125
}
125126

126127
// If node is not in a state that can become running, fail fast
127-
if (!_lightningState.value.nodeLifecycleState.canRun()) {
128+
if (!nodeLifecycleState.canRun()) {
128129
return@withContext Result.failure(
129-
Exception(
130-
"Cannot execute $operationName: Node is ${_lightningState.value.nodeLifecycleState} and not starting"
131-
)
130+
Exception("Cannot execute '$operationName': node is '$nodeLifecycleState' and not starting")
132131
)
133132
}
134133

135134
val nodeRunning = withTimeoutOrNull(waitTimeout) {
136-
if (_lightningState.value.nodeLifecycleState.isRunning()) {
135+
if (nodeLifecycleState.isRunning()) {
137136
return@withTimeoutOrNull true
138137
}
139138

140139
// Otherwise, wait for it to transition to running state
141-
Logger.verbose("Waiting for node runs to execute $operationName", context = TAG)
140+
Logger.verbose("Waiting for node to run before executing '$operationName'", context = TAG)
142141
_lightningState.first { it.nodeLifecycleState.isRunning() }
143-
Logger.debug("Operation executed: $operationName", context = TAG)
142+
Logger.debug("Operation executed: '$operationName'", context = TAG)
144143
true
145144
} ?: false
146145

@@ -159,7 +158,7 @@ class LightningRepo @Inject constructor(
159158
// Cancellation is expected during pull-to-refresh, rethrow per Kotlin best practices
160159
throw e
161160
} catch (e: Throwable) {
162-
Logger.error("$operationName error", e, context = TAG)
161+
Logger.error("Error executing '$operationName'", e, context = TAG)
163162
Result.failure(e)
164163
}
165164
}
@@ -312,7 +311,7 @@ class LightningRepo @Inject constructor(
312311
}
313312
}
314313

315-
suspend fun sync(): Result<Unit> = executeWhenNodeRunning("Sync") {
314+
suspend fun sync(): Result<Unit> = executeWhenNodeRunning("sync") {
316315
// If sync is in progress, mark pending and skip
317316
if (!syncMutex.tryLock()) {
318317
syncPending.set(true)
@@ -531,7 +530,7 @@ class LightningRepo @Inject constructor(
531530
return@withContext Result.success(Unit)
532531
}
533532

534-
suspend fun connectToTrustedPeers(): Result<Unit> = executeWhenNodeRunning("Connect to trusted peers") {
533+
suspend fun connectToTrustedPeers(): Result<Unit> = executeWhenNodeRunning("connectToTrustedPeers") {
535534
lightningService.connectToTrustedPeers()
536535
Result.success(Unit)
537536
}
@@ -544,13 +543,13 @@ class LightningRepo @Inject constructor(
544543
Result.success(Unit)
545544
}
546545

547-
suspend fun disconnectPeer(peer: PeerDetails): Result<Unit> = executeWhenNodeRunning("Disconnect peer") {
546+
suspend fun disconnectPeer(peer: PeerDetails): Result<Unit> = executeWhenNodeRunning("disconnectPeer") {
548547
lightningService.disconnectPeer(peer)
549548
syncState()
550549
Result.success(Unit)
551550
}
552551

553-
suspend fun newAddress(): Result<String> = executeWhenNodeRunning("New address") {
552+
suspend fun newAddress(): Result<String> = executeWhenNodeRunning("newAddress") {
554553
val address = lightningService.newAddress()
555554
Result.success(address)
556555
}
@@ -559,7 +558,7 @@ class LightningRepo @Inject constructor(
559558
amountSats: ULong? = null,
560559
description: String,
561560
expirySeconds: UInt = 86_400u,
562-
): Result<String> = executeWhenNodeRunning("Create invoice") {
561+
): Result<String> = executeWhenNodeRunning("createInvoice") {
563562
updateGeoBlockState()
564563
val invoice = lightningService.receive(amountSats, description, expirySeconds)
565564
Result.success(invoice)
@@ -631,12 +630,14 @@ class LightningRepo @Inject constructor(
631630
Logger.error("Error requesting lnurl auth, k1: $k1, callback: $callback, domain: $domain", it)
632631
}
633632

634-
suspend fun payInvoice(bolt11: String, sats: ULong? = null): Result<PaymentId> =
635-
executeWhenNodeRunning("Pay invoice") {
636-
val paymentId = lightningService.send(bolt11 = bolt11, sats = sats)
637-
syncState()
638-
Result.success(paymentId)
639-
}
633+
suspend fun payInvoice(
634+
bolt11: String,
635+
sats: ULong? = null,
636+
): Result<PaymentId> = executeWhenNodeRunning("payInvoice") {
637+
val paymentId = lightningService.send(bolt11 = bolt11, sats = sats)
638+
syncState()
639+
Result.success(paymentId)
640+
}
640641

641642
@Suppress("LongParameterList")
642643
suspend fun sendOnChain(
@@ -649,46 +650,45 @@ class LightningRepo @Inject constructor(
649650
channelId: String? = null,
650651
isMaxAmount: Boolean = false,
651652
tags: List<String> = emptyList(),
652-
): Result<Txid> =
653-
executeWhenNodeRunning("sendOnChain") {
654-
require(address.isNotEmpty()) { "Send address cannot be empty" }
653+
): Result<Txid> = executeWhenNodeRunning("sendOnChain") {
654+
require(address.isNotEmpty()) { "Send address cannot be empty" }
655655

656-
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
657-
val satsPerVByte = getFeeRateForSpeed(transactionSpeed, feeRates).getOrThrow().toUInt()
656+
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
657+
val satsPerVByte = getFeeRateForSpeed(transactionSpeed, feeRates).getOrThrow().toUInt()
658658

659-
// if utxos are manually specified, use them, otherwise run auto coin select if enabled
660-
val finalUtxosToSpend = utxosToSpend ?: determineUtxosToSpend(
661-
sats = sats,
662-
satsPerVByte = satsPerVByte,
663-
)
659+
// if utxos are manually specified, use them, otherwise run auto coin select if enabled
660+
val finalUtxosToSpend = utxosToSpend ?: determineUtxosToSpend(
661+
sats = sats,
662+
satsPerVByte = satsPerVByte,
663+
)
664664

665-
Logger.debug("UTXOs selected to spend: $finalUtxosToSpend", context = TAG)
665+
Logger.debug("UTXOs selected to spend: $finalUtxosToSpend", context = TAG)
666666

667-
val txId = lightningService.send(
668-
address = address,
669-
sats = sats,
670-
satsPerVByte = satsPerVByte,
671-
utxosToSpend = finalUtxosToSpend,
672-
isMaxAmount = isMaxAmount
673-
)
667+
val txId = lightningService.send(
668+
address = address,
669+
sats = sats,
670+
satsPerVByte = satsPerVByte,
671+
utxosToSpend = finalUtxosToSpend,
672+
isMaxAmount = isMaxAmount
673+
)
674674

675-
val preActivityMetadata = PreActivityMetadata(
676-
paymentId = txId,
677-
createdAt = nowTimestamp().toEpochMilli().toULong(),
678-
tags = tags,
679-
paymentHash = null,
680-
txId = txId,
681-
address = address,
682-
isReceive = false,
683-
feeRate = satsPerVByte.toULong(),
684-
isTransfer = isTransfer,
685-
channelId = channelId ?: "",
686-
)
687-
preActivityMetadataRepo.addPreActivityMetadata(preActivityMetadata)
675+
val preActivityMetadata = PreActivityMetadata(
676+
paymentId = txId,
677+
createdAt = nowTimestamp().toEpochMilli().toULong(),
678+
tags = tags,
679+
paymentHash = null,
680+
txId = txId,
681+
address = address,
682+
isReceive = false,
683+
feeRate = satsPerVByte.toULong(),
684+
isTransfer = isTransfer,
685+
channelId = channelId ?: "",
686+
)
687+
preActivityMetadataRepo.addPreActivityMetadata(preActivityMetadata)
688688

689-
syncState()
690-
Result.success(txId)
691-
}
689+
syncState()
690+
Result.success(txId)
691+
}
692692

693693
suspend fun determineUtxosToSpend(
694694
sats: ULong,
@@ -731,7 +731,7 @@ class LightningRepo @Inject constructor(
731731
Result.success(payments)
732732
}
733733

734-
suspend fun getAddressBalance(address: String): Result<ULong> = executeWhenNodeRunning("Get address balance") {
734+
suspend fun getAddressBalance(address: String): Result<ULong> = executeWhenNodeRunning("getAddressBalance") {
735735
runCatching {
736736
lightningService.getAddressBalance(address)
737737
}
@@ -787,7 +787,7 @@ class LightningRepo @Inject constructor(
787787

788788
suspend fun calculateCpfpFeeRate(
789789
parentTxId: Txid,
790-
): Result<ULong> = executeWhenNodeRunning("Calculate CPFP fee rate") {
790+
): Result<ULong> = executeWhenNodeRunning("calculateCpfpFeeRate") {
791791
Result.success(lightningService.calculateCpfpFeeRate(parentTxid = parentTxId).toSatPerVbCeil())
792792
}
793793

@@ -796,7 +796,7 @@ class LightningRepo @Inject constructor(
796796
channelAmountSats: ULong,
797797
pushToCounterpartySats: ULong? = null,
798798
channelConfig: ChannelConfig? = null,
799-
): Result<OpenChannelResult> = executeWhenNodeRunning("Open channel") {
799+
): Result<OpenChannelResult> = executeWhenNodeRunning("openChannel") {
800800
val result = lightningService.openChannel(peer, channelAmountSats, pushToCounterpartySats, channelConfig)
801801
syncState()
802802
result
@@ -931,25 +931,19 @@ class LightningRepo @Inject constructor(
931931
try {
932932
if (originalTxId.isBlank()) {
933933
return@executeWhenNodeRunning Result.failure(
934-
IllegalArgumentException(
935-
"originalTxId is null or empty: $originalTxId"
936-
)
934+
IllegalArgumentException("originalTxId is null or empty: $originalTxId")
937935
)
938936
}
939937

940938
if (destinationAddress.isBlank()) {
941939
return@executeWhenNodeRunning Result.failure(
942-
IllegalArgumentException(
943-
"destinationAddress is null or empty: $destinationAddress"
944-
)
940+
IllegalArgumentException("destinationAddress is null or empty: $destinationAddress")
945941
)
946942
}
947943

948944
if (satsPerVByte <= 0u) {
949945
return@executeWhenNodeRunning Result.failure(
950-
IllegalArgumentException(
951-
"satsPerVByte invalid: $satsPerVByte"
952-
)
946+
IllegalArgumentException("satsPerVByte invalid: $satsPerVByte")
953947
)
954948
}
955949

0 commit comments

Comments
 (0)