Skip to content

Conversation

@xbuddhi
Copy link

@xbuddhi xbuddhi commented Nov 28, 2025

feat: Automated Release Workflow & Prevent Duplicate Transactions

🚀 Summary

This PR introduces a fully automated release pipeline using GitHub Actions and GoReleaser, AND implements a robust locking mechanism for the API send endpoint to prevent duplicate transactions.

✨ Key Changes

1. Prevent Duplicate Transactions (New)

  • Locking Mechanism: Implemented a locking mechanism for the API send endpoint using the internal cache.
  • Ongoing Transaction Check: Checks MemoCache to see if a transaction with the provided memo is currently processing. Returns "Transaction ongoing" if found.
  • Completed Transaction Check: Checks the database for any successful transactions with the same memo. Returns "Transaction already completed" if found.
  • Cache Update: Added Delete method to Cache struct and initialized MemoCache in main.go.

2. Automated Release Workflow

  • New Workflow (.github/workflows/release.yml):

    • Trigger: Activates only when a PR is closed and merged into main.
    • Auto-Versioning: Uses mathieudutour/github-tag-action to automatically bump the patch version.
    • Release Generation: Uses GoReleaser to build the binary and create a GitHub Release.
    • Notifications: Sends a formatted message to multiple Telegram groups on Success only.
      • Uses MAIN_BOT_TOKEN secret for the bot token.
      • Uses RELEASE_CHAT_IDS secret for chat IDs.
      • Supports Threads: RELEASE_CHAT_IDS supports multiple comma-separated chat IDs, optionally with thread IDs (e.g., -100123,-100456:7).
  • GoReleaser Config (.goreleaser.yaml):

    • Configured to build for Linux/AMD64 with CGO_ENABLED=1.
    • Optimized build flags (-s -w).

🧪 Testing

  • Verified GoReleaser configuration syntax.
  • Workflow triggers are set to pull_request (closed + merged).
  • Verified API locking mechanism prevents duplicate requests with same memo (ongoing).
  • Verified API prevents duplicate requests if transaction is already in DB (completed).
  • Verified Telegram notification logic supports multiple comma-separated group IDs and thread IDs (e.g., chat_id:thread_id).

Summary by CodeRabbit

  • New Features

    • Memo-based duplicate-transaction protection to prevent concurrent or repeated sends.
    • Cache primitives: atomic set-if-not-exists and explicit delete; memo cache initialized at startup.
  • Chores

    • Automated release pipelines for main and dev: tagging, prerelease/dev support, GoReleaser publishing, and Telegram notifications to multiple targets (comma-separated, optional thread IDs).

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds CI release and dev-release workflows and a GoReleaser config; introduces a MemoCache (*utils.Cache) initialized in main; adds SetNX/Delete to the cache; and enforces memo-based in-flight locking and duplicate-memo checks in the /api/send handler.

Changes

Cohort / File(s) Summary
Release workflows
\.github/workflows/release.yml, \.github/workflows/release-dev.yml
New workflows: release.yml triggers on merged PRs to main (bump tag, run GoReleaser, notify Telegram). release-dev.yml triggers on dev pushes (dev tag, GoReleaser dry-run/dev release, notify Telegram).
GoReleaser config
.goreleaser.yaml
New GoReleaser configuration for project BitcoinDeepaBot: linux/amd64 build (CGO_ENABLED), ldflags, checksum template, snapshot/changelog settings and filters.
API service — MemoCache wiring
internal/api/lightning.go, main.go
Service struct gains exported MemoCache *utils.Cache; main.go initializes it with utils.NewCache(5 * time.Minute).
Send handler — memo concurrency control
internal/api/send.go
Adds memo-based SetNX locking for non-empty memos, checks DB for completed transactions with memo suffix, returns 409 for in-flight or duplicate memos, ensures lock removal on completion/error, and logs conditions.
Cache utility — SetNX & Delete
internal/utils/cache.go
Adds SetNX(key string, value string) bool (set-if-not-exists with TTL semantics) and Delete(key string) (remove key under mutex).

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub
    participant GHA as GitHub Actions
    participant GR as GoReleaser
    participant TG as Telegram

    Dev->>GH: Merge PR to main
    GH->>GHA: Trigger release workflow
    activate GHA
    GHA->>GHA: Checkout repo (fetch-depth:0)
    GHA->>GHA: Setup Go 1.21
    GHA->>GHA: Bump version & create tag
    GHA->>GR: Run GoReleaser (build & publish)
    activate GR
    GR-->>GHA: Artifacts published
    deactivate GR
    alt Release success
        GHA->>TG: Send success message (version, PR title, author, release link)
        TG-->>GHA: Ack
    end
    deactivate GHA
Loading
sequenceDiagram
    participant Client as Client
    participant API as API Service
    participant Cache as MemoCache
    participant DB as Database

    Client->>API: POST /api/send (memo X)
    API->>Cache: SetNX "memo:X"
    alt SetNX returns false
        Cache-->>API: lock exists
        API-->>Client: 409 "transaction already processing"
    else SetNX true
        Cache-->>API: lock acquired
        API->>DB: Query completed transactions WHERE memo LIKE '%X'
        alt Completed found
            DB-->>API: existing record
            API->>Cache: Delete "memo:X"
            API-->>Client: 409 "memo already used"
        else None found
            API->>API: proceed with send flow (payment processing, DB writes)
            API->>Cache: Delete "memo:X" (on completion or error)
            API-->>Client: 200 / appropriate response
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus areas:
    • internal/api/send.go: verify lock is removed on every code path (including panics), correct error codes/messages, and correctness of memo-suffix DB query.
    • internal/utils/cache.go: check SetNX atomicity, TTL expiry correctness, and concurrent safety of Delete.
    • CI workflows: verify tag creation/push steps, required permissions, and handling of multiple Telegram targets (chat_id vs chat_id:thread_id) and secrets.

Possibly related PRs

Suggested labels

Review effort 3/5, Possible security concern

Suggested reviewers

  • cmnisal

Poem

🐇 I hopped through branches, set a tiny key,
Memos locked neatly so twins cannot be,
Tags rose on clouds and builds sang a tune,
Cache cleared its traces beneath the moon,
A happy rabbit celebrates release soon! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures both main changes: automated release workflow and duplicate transaction prevention, directly reflected in the file modifications and PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d85c77a and c24dca7.

📒 Files selected for processing (1)
  • .github/workflows/release-dev.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release-dev.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

qodo-code-review bot commented Nov 28, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Markdown injection risk

Description: Telegram notifications use Markdown parse mode without robust escaping, so PR titles,
actor names, or tags containing characters like [, ], (, ), _, *, or links could break
formatting or enable unintended Markdown rendering, potentially leaking or spoofing
content in the chat; sanitize or escape dynamic fields before sending.
release.yml [46-60]

Referred Code
- name: Notify Telegram Success
  if: success()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
      -d parse_mode="Markdown"

- name: Notify Telegram Failure
  if: failure()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
      -d parse_mode="Markdown"
Workflow permission abuse

Description: The workflow auto-bumps and tags on every merged PR using the default GitHub token, which
by default has repository write scope and can create tags/releases; if this workflow is
allowed to run from forks or untrusted contexts, it could be abused to push tags or
trigger releases—ensure the workflow is not reusable by forks and restrict permissions and
branch protection.
release.yml [30-45]

Referred Code
- name: Bump version and push tag
  id: tag_version
  uses: mathieudutour/github-tag-action@v6.1
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    default_bump: patch

- name: Run GoReleaser
  uses: goreleaser/goreleaser-action@v5
  with:
    distribution: goreleaser
    version: latest
    args: release --clean
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action Auditing: The workflow performs critical release actions and notifications without explicit audit
logging beyond GitHub Actions’ default logs, making it unclear if sufficient structured
audit trails exist.

Referred Code
jobs:
  release:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    env:
      BOT_TOKEN: ${{ secrets.DEV_ACTIONS_BOT_TOKEN }}
      CHAT_ID: ${{ secrets.DEV_ACTIONS_GROUP_ID }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'

      - name: Bump version and push tag
        id: tag_version
        uses: mathieudutour/github-tag-action@v6.1


 ... (clipped 28 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Failure Context: Failure notification sends a generic message without surfacing actionable context (e.g.,
failing step or error output), which may hinder debugging.

Referred Code
- name: Notify Telegram Failure
  if: failure()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
      -d parse_mode="Markdown"

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Secret Exposure Risk: Telegram messages interpolate PR title and actor to an external chat; while not PII per
se, sending potentially sensitive metadata to third-party chat may violate secure
logging/telemetry practices.

Referred Code
- name: Notify Telegram Success
  if: success()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
      -d parse_mode="Markdown"

- name: Notify Telegram Failure
  if: failure()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
      -d parse_mode="Markdown"

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
External Output: Workflow sends dynamic inputs (PR title, actor, tag) to Telegram without explicit
sanitization; while Markdown mode is used, validation/escaping for special characters is
not shown.

Referred Code
- name: Notify Telegram Success
  if: success()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
      -d parse_mode="Markdown"

- name: Notify Telegram Failure
  if: failure()
  run: |
    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
      -d chat_id="${{ env.CHAT_ID }}" \
      -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
      -d parse_mode="Markdown"

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Nov 28, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Add concurrency control to workflow

To prevent failures from race conditions when multiple PRs are merged, add a
concurrency block to the workflow. This ensures only one release job can run at
a time.

Examples:

.github/workflows/release.yml [3-14]
on:
  pull_request:
    types: [closed]
    branches:
      - main

permissions:
  contents: write

jobs:

 ... (clipped 2 lines)

Solution Walkthrough:

Before:

# .github/workflows/release.yml
name: Release

on:
  pull_request:
    types: [closed]
    branches:
      - main

permissions:
  contents: write

jobs:
  release:
    if: github.event.pull_request.merged == true
    ...

After:

# .github/workflows/release.yml
name: Release

on:
  pull_request:
    types: [closed]
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false

permissions:
  contents: write

jobs:
  release:
    if: github.event.pull_request.merged == true
    ...
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical race condition that could cause the new release workflow to fail under common circumstances, and proposes the standard and correct solution to ensure its reliability.

High
Security
Prevent command injection in notifications
Suggestion Impact:The commit added PR_TITLE to env and used ${PR_TITLE} instead of interpolating the raw GitHub context directly in the curl text payload. Although it did not adopt --data-urlencode, it partially implemented the suggestion by moving the user-provided title into an environment variable and referencing it safely in the notification commands.

code diff:

+      PR_TITLE: ${{ github.event.pull_request.title }}
     steps:
       - name: Checkout
         uses: actions/checkout@v4
@@ -23,7 +24,7 @@
           fetch-depth: 0
 
       - name: Set up Go
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v6
         with:
           go-version: '1.21'
 
@@ -33,6 +34,9 @@
         with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           default_bump: patch
+
+      - name: Create local tag
+        run: git tag ${{ steps.tag_version.outputs.new_tag }}
 
       - name: Run GoReleaser
         uses: goreleaser/goreleaser-action@v5
@@ -46,15 +50,21 @@
       - name: Notify Telegram Success
         if: success()
         run: |
-          curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
-            -d chat_id="${{ env.CHAT_ID }}" \
-            -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
-            -d parse_mode="Markdown"
-
-      - name: Notify Telegram Failure
-        if: failure()
-        run: |
-          curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
-            -d chat_id="${{ env.CHAT_ID }}" \
-            -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
-            -d parse_mode="Markdown"
+          IFS=',' read -ra TARGETS <<< "${{ env.CHAT_ID }}"
+          for target in "${TARGETS[@]}"; do
+            target=$(echo "$target" | xargs)
+            if [[ "$target" == *":"* ]]; then
+              chat_id=${target%%:*}
+              thread_id=${target#*:}
+              curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
+                -d chat_id="$chat_id" \
+                -d message_thread_id="$thread_id" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d parse_mode="Markdown"
+            else
+              curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
+                -d chat_id="$target" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d parse_mode="Markdown"

To prevent a command injection vulnerability, pass the user-provided PR title as
an environment variable to the curl steps and use --data-urlencode to safely
include it in the request body.

.github/workflows/release.yml [46-60]

 - name: Notify Telegram Success
   if: success()
+  env:
+    PR_TITLE: ${{ github.event.pull_request.title }}
   run: |
-    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
-      -d chat_id="${{ env.CHAT_ID }}" \
-      -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
-      -d parse_mode="Markdown"
+    MESSAGE="🚀 *New Release Published!*
+    *Version:* ${{ steps.tag_version.outputs.new_tag }}
+    *Title:* $PR_TITLE
+    *Author:* ${{ github.actor }}
+
+    [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})"
+
+    curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
+      --data-urlencode "chat_id=${CHAT_ID}" \
+      --data-urlencode "text=${MESSAGE}" \
+      --data-urlencode "parse_mode=Markdown"
 
 - name: Notify Telegram Failure
   if: failure()
+  env:
+    PR_TITLE: ${{ github.event.pull_request.title }}
   run: |
-    curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
-      -d chat_id="${{ env.CHAT_ID }}" \
-      -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${{ github.event.pull_request.title }}" \
-      -d parse_mode="Markdown"
+    MESSAGE="❌ *Release Failed!*
 
+    The release workflow failed for PR: $PR_TITLE"
+
+    curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
+      --data-urlencode "chat_id=${CHAT_ID}" \
+      --data-urlencode "text=${MESSAGE}" \
+      --data-urlencode "parse_mode=Markdown"
+

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical command injection vulnerability and proposes a robust fix by using environment variables and proper URL encoding, which significantly enhances the security of the workflow.

High
  • Update

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

49-60: Consider adding error handling to curl commands.

The Telegram notification curl commands don't check HTTP response codes. Consider adding the -f flag to fail on HTTP error responses, or check the response explicitly, to ensure failures are surfaced rather than silently masked.

Apply this diff to add error handling:

       - name: Notify Telegram Success
         if: success()
         run: |
-          curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
+          curl -sf -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
             -d chat_id="${{ env.CHAT_ID }}" \
             -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
             -d parse_mode="Markdown"
 
       - name: Notify Telegram Failure
         if: failure()
         run: |
-          curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
+          curl -sf -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
             -d chat_id="${{ env.CHAT_ID }}" \
-            -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${PR_TITLE}" \
+            -d text="❌ *Release Failed!*%0A%0AThe release workflow failed for PR: ${PR_TITLE}" \
             -d parse_mode="Markdown"

The -f flag causes curl to return a non-zero exit code on HTTP errors (4xx/5xx), which will fail the step if the Telegram API returns an error.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ffa8a4 and a9e0122.

📒 Files selected for processing (2)
  • .github/workflows/release.yml (1 hunks)
  • .goreleaser.yaml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.9)
.github/workflows/release.yml

26-26: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


48-48: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)


56-56: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
.goreleaser.yaml (1)

1-24: GoReleaser configuration is well-structured.

The build configuration appropriately enables CGO for C bindings, targets linux/amd64, and uses standard flags for static compilation. The linker flags (-s -w) correctly strip symbols to minimize binary size. Changelog filtering excludes non-feature commits as intended.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/api/send.go (1)

252-288: Consider: Admin approval flow releases memo lock immediately.

When a transaction requires admin approval, the function returns at line 287, triggering the deferred Delete(memoLockKey) and releasing the memo lock. This allows subsequent requests with the same memo to create additional pending transactions and approval requests before the first is approved or rejected.

If the intent is to prevent duplicate approval requests for the same memo, the lock should remain until the transaction is actually approved and executed (or rejected). This would require a different locking strategy, such as:

  1. Storing the pending transaction ID in the cache instead of "locked"
  2. Checking for pending transactions with the same memo at the start
  3. Releasing the lock only after approval/rejection

Is this intended behavior, or should duplicate approval requests with the same memo be prevented?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9e0122 and 0469453.

📒 Files selected for processing (4)
  • internal/api/lightning.go (1 hunks)
  • internal/api/send.go (2 hunks)
  • internal/utils/cache.go (1 hunks)
  • main.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
internal/utils/cache.go (1)
internal/runtime/mutex/mutex.go (2)
  • Lock (110-127)
  • Unlock (130-141)
main.go (4)
internal/api/lightning.go (1)
  • Service (17-20)
internal/api/userpage/userpage.go (1)
  • Service (20-22)
internal/api/admin/admin.go (1)
  • Service (7-9)
internal/utils/cache.go (1)
  • NewCache (19-24)
internal/api/lightning.go (2)
internal/telegram/bot.go (2)
  • TipBot (25-34)
  • Cache (35-37)
internal/utils/cache.go (1)
  • Cache (13-17)
internal/api/send.go (2)
internal/api/send_config.go (1)
  • GetWhitelistedWallets (8-10)
internal/api/lightning.go (1)
  • RespondError (26-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: build-and-deploy
🔇 Additional comments (4)
internal/utils/cache.go (1)

47-51: LGTM! Delete method implementation is correct.

The Delete method correctly follows the mutex locking pattern used by Set and Get, ensuring thread-safe deletion of cache entries.

main.go (1)

94-97: LGTM! MemoCache initialization is correct.

The MemoCache is properly initialized with a 5-minute TTL, which is reasonable for preventing duplicate memo processing while not holding locks indefinitely.

internal/api/lightning.go (1)

18-19: LGTM! Service struct extension is appropriate.

The MemoCache field is correctly added to the Service struct and properly initialized in main.go.

internal/api/send.go (1)

154-154: Consider: Lock granularity may be too coarse.

The memo lock key is based solely on the memo text, meaning all API sends with the same memo (e.g., "lunch", "coffee") are blocked regardless of sender or recipient. If this is intended for global memo uniqueness, this is correct. However, if the intent is to prevent duplicate sends from the same wallet, consider including the wallet ID:

-memoLockKey := fmt.Sprintf("api_send_memo_%s", req.Memo)
+memoLockKey := fmt.Sprintf("api_send_memo_%s_%s", walletID, req.Memo)

This would allow different wallets to use the same memo concurrently.

Please clarify the intended scope: Should memo locking be global (current behavior) or per-wallet?

@xbuddhi xbuddhi changed the title feat: Automated Release Workflow & Telegram Notifications feat: Automated Release Workflow & Prevent Duplicate Transactions Nov 29, 2025
Copy link

@helloscoopa helloscoopa left a comment

Choose a reason for hiding this comment

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

Issue raised by coderabbit needs to be taken care of, apart from that the implementation looks good:

Implementation Quality:

  • Thread-safe operations throughout
  • Proper error handling and logging
  • Fail-closed security posture
  • Multiple layers of protection (in-flight lock + DB check + TTL)
  • Clean separation of concerns

…_RELEASE_CHAT_IDS) and support threads in Telegram notifications
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
.github/workflows/release.yml (2)

26-28: Update actions/setup-go from v4 to v6 for compatibility with current runners.

The workflow uses actions/setup-go@v4, which is outdated. Update to v6 to receive security patches and current Go version support.

uses: actions/setup-go@v6

48-66: Fix script injection vulnerability: use environment variable for github.event.pull_request.title.

github.event.pull_request.title is user-controlled and passed directly into the inline script on lines 58 and 63. Per GitHub's security guidelines, this should be passed through an environment variable to prevent command injection attacks.

Apply this diff to secure the notification step:

 jobs:
   release:
     if: github.event.pull_request.merged == true
     runs-on: ubuntu-latest
     env:
       BOT_TOKEN: ${{ secrets.MAIN_BOT_TOKEN }}
       CHAT_ID: ${{ vars.RELEASE_CHAT_IDS }}
+      PR_TITLE: ${{ github.event.pull_request.title }}
     steps:
       - name: Checkout
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
       - name: Set up Go
         uses: actions/setup-go@v4
         with:
           go-version: '1.21'
 
       - name: Bump version and push tag
         id: tag_version
         uses: mathieudutour/github-tag-action@v6.1
         with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           default_bump: patch
 
       - name: Run GoReleaser
         uses: goreleaser/goreleaser-action@v5
         with:
           distribution: goreleaser
           version: latest
           args: release --clean
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Notify Telegram Success
         if: success()
         run: |
           IFS=',' read -ra TARGETS <<< "${{ env.CHAT_ID }}"
           for target in "${TARGETS[@]}"; do
             target=$(echo "$target" | xargs)
             if [[ "$target" == *":"* ]]; then
               chat_id=${target%%:*}
               thread_id=${target#*:}
               curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
                 -d chat_id="$chat_id" \
                 -d message_thread_id="$thread_id" \
-                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
                 -d parse_mode="Markdown"
             else
               curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
                 -d chat_id="$target" \
-                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
                 -d parse_mode="Markdown"
             fi
           done

Reference: https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0a75bd and debec87.

📒 Files selected for processing (2)
  • .github/workflows/release-dev.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.9)
.github/workflows/release.yml

26-26: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


48-48: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

.github/workflows/release-dev.yml

24-24: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (actions)
  • GitHub Check: build-and-deploy
🔇 Additional comments (1)
.github/workflows/release-dev.yml (1)

50-73: Telegram notification logic is well-designed and secure.

The comma-separated chat ID parsing with optional thread ID support (via chat_id:thread_id format) is correct and handles edge cases properly. The notification message is appropriately formatted and does not use untrusted inputs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/utils/cache.go (1)

47-68: Delete/SetNX implementations are concurrency-safe and match existing TTL semantics

Delete and SetNX correctly reuse the cache’s mutex, and SetNX provides the needed atomic “set if not exists or expired” behavior for memo locking. This is a solid fix for the earlier race condition risk.

If you want to reduce duplication later, you could have Set delegate to SetNX (or vice versa) so the TTL/assignment logic lives in one place, but it’s not required for correctness.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between debec87 and 7ba9fb4.

📒 Files selected for processing (2)
  • internal/api/send.go (2 hunks)
  • internal/utils/cache.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/api/send.go (4)
internal/api/send_config.go (1)
  • GetWhitelistedWallets (8-10)
internal/api/lightning.go (1)
  • RespondError (26-30)
internal/storage/bunt.go (1)
  • DB (16-18)
internal/telegram/transaction.go (1)
  • Transaction (13-34)
internal/utils/cache.go (1)
internal/runtime/mutex/mutex.go (2)
  • Lock (110-127)
  • Unlock (130-141)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: build-and-deploy
🔇 Additional comments (2)
internal/api/send.go (2)

121-129: Authenticated wallet extraction and whitelist lookup look good

Casting the authenticated wallet from context to string and validating it against GetWhitelistedWallets() is straightforward and consistent with the surrounding flow; no issues here.


153-183: Memo-based locking and duplicate check correctly fix the race

The memo block successfully implements both protections:

  • Uses SetNX on MemoCache to atomically acquire a per-memo lock; concurrent requests with the same memo fail fast with "already processing".
  • Deletes the memo lock via defer s.MemoCache.Delete(memoLockKey) on all return paths from this handler.
  • Adds a DB-level guard (LIKE on the memo suffix with success = true) to reject non-concurrent repeat calls as "already completed".

This removes the previous TOCTOU window and provides both "ongoing" and "already completed" protection. MemoCache is properly initialized in main.go as utils.NewCache(time.Minute * 5) with no nil pointer risk.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
.github/workflows/release.yml (2)

26-28: Update actions/setup-go from v4 to v6.1.0.

The workflow pins an outdated action version. Update to v6 (latest is v6.1.0) to receive security patches and ensure compatibility with current Go tooling.

- name: Set up Go
  uses: actions/setup-go@v6
  with:
    go-version: '1.21'

58-68: Fix script injection vulnerability: Pass PR title via environment variable.

github.event.pull_request.title is user-controlled and should not be interpolated directly into inline scripts. This creates a command injection attack vector. Pass it through an environment variable instead, as documented in GitHub's security guidelines.

 jobs:
   release:
     if: github.event.pull_request.merged == true
     runs-on: ubuntu-latest
     env:
       BOT_TOKEN: ${{ secrets.MAIN_BOT_TOKEN }}
       CHAT_ID: ${{ vars.RELEASE_CHAT_IDS }}
+      PR_TITLE: ${{ github.event.pull_request.title }}
     steps:
       ...
       - name: Notify Telegram Success
         if: success()
         run: |
           IFS=',' read -ra TARGETS <<< "${{ env.CHAT_ID }}"
           for target in "${TARGETS[@]}"; do
             target=$(echo "$target" | xargs)
             if [[ "$target" == *":"* ]]; then
               chat_id=${target%%:*}
               thread_id=${target#*:}
               curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
                 -d chat_id="$chat_id" \
                 -d message_thread_id="$thread_id" \
-                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
                 -d parse_mode="Markdown"
             else
               curl -s -X POST https://api.telegram.org/bot${{ env.BOT_TOKEN }}/sendMessage \
                 -d chat_id="$target" \
-                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${{ github.event.pull_request.title }}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
+                -d text="🚀 *New Release Published!*%0A%0A*Version:* ${{ steps.tag_version.outputs.new_tag }}%0A*Title:* ${PR_TITLE}%0A*Author:* ${{ github.actor }}%0A%0A[View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_version.outputs.new_tag }})" \
                 -d parse_mode="Markdown"
             fi
           done
.github/workflows/release-dev.yml (1)

23-26: Update actions/setup-go from v4 to v6.1.0.

The workflow pins an outdated action version. Update to v6 (latest is v6.1.0) to receive security patches and ensure compatibility with current Go tooling.

- name: Set up Go
  uses: actions/setup-go@v6
  with:
    go-version: '1.21'
🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

49-69: Add guard for empty CHAT_ID targets to prevent unnecessary curl calls.

The dev workflow includes a guard (if [[ -z "$target" ]]) to skip empty targets after trimming whitespace. Add the same safeguard here for robustness.

  - name: Notify Telegram Success
    if: success()
    run: |
      IFS=',' read -ra TARGETS <<< "${{ env.CHAT_ID }}"
      for target in "${TARGETS[@]}"; do
        target=$(echo "$target" | xargs)
+       if [[ -z "$target" ]]; then
+         continue
+       fi
        if [[ "$target" == *":"* ]]; then
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5f488 and 13897a5.

📒 Files selected for processing (2)
  • .github/workflows/release-dev.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.9)
.github/workflows/release-dev.yml

24-24: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

.github/workflows/release.yml

26-26: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


51-51: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: build-and-deploy
🔇 Additional comments (1)
.github/workflows/release-dev.yml (1)

61-84: Telegram notification logic is sound.

The dev release workflow correctly:

  • Parses comma-separated CHAT_IDs with optional thread_id support
  • Trims whitespace and skips empty targets (lines 67-69)
  • Uses commit SHA and tag (not user-controlled PR title), avoiding injection risks
  • Sends formatted Markdown notifications to all targets

This is a solid implementation for multi-target notifications.

Copy link

@helloscoopa helloscoopa left a comment

Choose a reason for hiding this comment

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

LGTM

@helloscoopa helloscoopa merged commit b3c684b into main Dec 2, 2025
5 of 6 checks passed
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.

4 participants