Skip to content

feat(docker): add Docker support with Dockerfile and docker-compose c… - #48

Merged
gosunuts merged 3 commits into
mainfrom
feature/docker-build
Aug 11, 2026
Merged

feat(docker): add Docker support with Dockerfile and docker-compose c…#48
gosunuts merged 3 commits into
mainfrom
feature/docker-build

Conversation

@gosunuts

Copy link
Copy Markdown
Member

…onfiguration

@gosunuts
gosunuts requested a review from metaphorics August 11, 2026 07:30
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@gosunuts, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a78275ad-02b5-427d-9e4b-418746eb1700

📥 Commits

Reviewing files that changed from the base of the PR and between 82c1030 and 3ff421d.

📒 Files selected for processing (1)
  • .env.example
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Docker support for building and running a bitcoin-rs node.
    • Added configurable Bitcoin network, RPC/P2P ports, credentials, and log levels.
    • Added persistent blockchain storage and an RPC-based health check.
    • Added an unprivileged runtime container with exposed RPC and P2P endpoints.
  • Documentation

    • Added Docker Compose quick-start instructions, startup and logging commands, sync verification, and shutdown guidance.
    • Documented container security, network-specific storage, endpoint exposure, and shutdown behavior.
  • Chores

    • Added environment and Docker ignore rules to keep sensitive and generated files out of version control.

Walkthrough

The change adds a multi-stage Docker build and Docker Compose setup for bitcoin-rs. It adds configurable environment variables, persistent network-specific storage, RPC health checks, Docker ignore rules, and deployment instructions.

Changes

Docker deployment

Layer / File(s) Summary
Build and runtime image
Dockerfile
The multi-stage build compiles a locked release binary with the fjall and kernel features. The runtime image uses Debian, an unprivileged bitcoin-rs user, /data, and RPC/P2P ports.
Compose runtime orchestration
docker-compose.yaml, .env.example
Docker Compose builds and runs the node with environment-based network, port, credential, and log settings. A network-specific named volume persists /data, and an RPC health check queries getblockchaininfo.
Deployment configuration and instructions
.dockerignore, .gitignore, README.md, CONCEPTS.md
Ignore rules exclude local environment files and build artifacts. The documentation covers setup, startup, logs, synchronization checks, authentication, and volume-preserving or volume-deleting shutdown commands.

Sequence Diagram(s)

sequenceDiagram
  participant DockerCompose
  participant BitcoinRS
  participant NamedVolume
  participant RPCClient
  DockerCompose->>BitcoinRS: build and start configured service
  BitcoinRS->>NamedVolume: read and write network-specific chain data
  RPCClient->>BitcoinRS: query getblockchaininfo
  BitcoinRS-->>RPCClient: return synchronization state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description is only an incomplete fragment and does not meaningfully describe the Docker changes. Replace the fragment with a concise summary of the Dockerfile, Docker Compose configuration, and deployment documentation.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the Conventional Commits format and clearly describes the Docker support added by the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/docker-build

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker-compose.yaml`:
- Line 16: Update the BITCOIN_RS_RPC_PASSWORD environment assignment in the
Docker Compose configuration to require a non-empty value using the specified
mandatory-variable syntax, removing the hardcoded bitcoin-rs fallback while
preserving the existing variable name.

In `@README.md`:
- Around line 58-62: Update the README RPC example by removing the `. ./.env`
sourcing and running the curl request inside the node container with the
Compose-provided RPC variables. Use the container execution command and the
internal Bitcoin RPC endpoint so the example no longer evaluates environment
contents or depends on a configurable host RPC port.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c4b7d4c-c2a3-414f-a992-7c44decd1790

📥 Commits

Reviewing files that changed from the base of the PR and between 6113a69 and a6c3233.

📒 Files selected for processing (6)
  • .dockerignore
  • .env.example
  • .gitignore
  • Dockerfile
  • README.md
  • docker-compose.yaml
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: test
  • GitHub Check: bench-smoke
🧰 Additional context used
🪛 Betterleaks (1.7.3)
docker-compose.yaml

[high] 29-30: Discovered a potential basic authorization token provided in a curl command, which could compromise the curl accessed resource.

(curl-auth-user)

🪛 dotenv-linter (4.0.0)
.env.example

[warning] 7-7: [UnorderedKey] The BITCOIN_RS_RPC_PASSWORD key should go before the BITCOIN_RS_RPC_PORT key

(UnorderedKey)

🔍 Remote MCP Context7

Additional review context

  • Compose interpolates ${VAR} by default; ${VAR:-default} and ${VAR:?error} are supported, while $$ escapes literal dollar signs. Verify all variables and command strings use the intended form.
  • Named volumes persist data beyond container removal; confirm /data is both the mounted path and writable by the unprivileged runtime user.
  • EXPOSE is metadata only; actual publishing comes from Compose ports. Binding RPC to 127.0.0.1 restricts host access, whereas P2P should remain externally reachable if peer connectivity is required.
  • In a multi-stage build, only explicitly copied artifacts reach the final image, so every required native runtime library must be installed there.
  • Bitcoin’s getblockchaininfo exposes synchronization-related fields such as verificationprogress and initialblockdownload; an RPC-success healthcheck alone confirms reachability, not synchronization completion.
🔇 Additional comments (6)
Dockerfile (1)

1-45: LGTM!

docker-compose.yaml (1)

1-15: LGTM!

Also applies to: 18-40

.env.example (1)

1-13: LGTM!

.dockerignore (1)

1-12: LGTM!

.gitignore (1)

17-17: LGTM!

README.md (1)

39-55: LGTM!

Also applies to: 65-66

Comment thread docker-compose.yaml Outdated
BITCOIN_RS_NETWORK: "${BITCOIN_RS_NETWORK:-mainnet}"
BITCOIN_RS_STORAGE_BACKEND: fjall
BITCOIN_RS_RPC_USER: "${BITCOIN_RS_RPC_USER:-bitcoin-rs}"
BITCOIN_RS_RPC_PASSWORD: "${BITCOIN_RS_RPC_PASSWORD:-bitcoin-rs}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the fallback RPC password.

Line 16 starts the node with the known password bitcoin-rs when the variable is unset or empty. .env.example leaves the value empty, so this path is easy to reach. Host-local clients can then authenticate with public credentials.

Require a non-empty BITCOIN_RS_RPC_PASSWORD with ${BITCOIN_RS_RPC_PASSWORD:?set BITCOIN_RS_RPC_PASSWORD in .env}. Do not provide a fixed fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yaml` at line 16, Update the BITCOIN_RS_RPC_PASSWORD
environment assignment in the Docker Compose configuration to require a
non-empty value using the specified mandatory-variable syntax, removing the
hardcoded bitcoin-rs fallback while preserving the existing variable name.

Source: MCP tools

Comment thread README.md Outdated
@metaphorics

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6c32332ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md Outdated
Comment on lines +41 to +43
The included Compose configuration builds the production `fjall` +
`bitcoinkernel` profile, keeps chain state in a named volume, exposes P2P on
port 8333, and binds RPC to the Docker host's loopback interface only.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconcile the production Docker posture in CONCEPTS

This section establishes a durable container deployment configuration and explicitly calls it the production profile, but the affected Optimized default posture term in CONCEPTS.md is not reconciled with the container-specific volume, port, and RPC exposure decisions. Update that project vocabulary alongside this documentation so the two descriptions do not drift, as required by the repository guidance.

AGENTS.md reference: AGENTS.md:L5-L7

Useful? React with 👍 / 👎.

Comment thread README.md Outdated
curl --user "$BITCOIN_RS_RPC_USER:$BITCOIN_RS_RPC_PASSWORD" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"1.0","id":"sync","method":"getblockchaininfo","params":[]}' \
http://127.0.0.1:8332/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the configured host RPC port in the example

When BITCOIN_RS_RPC_PORT is changed from 8332, Compose publishes the RPC service on that configured host port (docker-compose.yaml:23), but this command still connects to 8332. The documented sync check therefore fails for a supported non-default .env; construct the URL using $BITCOIN_RS_RPC_PORT (with the same default) instead.

Useful? React with 👍 / 👎.

Comment thread docker-compose.yaml Outdated
Comment thread docker-compose.yaml
Comment thread docker-compose.yaml Outdated
@gosunuts

Copy link
Copy Markdown
Member Author

@codex review

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Line 7: Remove the placeholder value from BITCOIN_RS_RPC_PASSWORD in the
environment template, and update startup validation in the application and
Compose configuration to reject unset, empty, or placeholder credentials,
requiring an operator-supplied secret.

In `@CONCEPTS.md`:
- Line 33: Update the Docker Compose RPC password configuration referenced by
the concepts description so BITCOIN_RS_RPC_PASSWORD is required and non-empty,
removing the current fallback to a predictable password. Use Compose’s
required-variable syntax or equivalent secret injection while preserving the
existing RPC configuration.
- Line 33: Update the Compose specialization description to accurately state
that BITCOIN_RS_RPC_PASSWORD defaults to "password" rather than requiring an
explicit non-empty password, and document the security impact of this fallback
if retaining it. Leave the existing shutdown behavior description unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 06d91a16-f3d4-403d-9e46-336d946b4084

📥 Commits

Reviewing files that changed from the base of the PR and between a6c3233 and 82c1030.

📒 Files selected for processing (4)
  • .env.example
  • CONCEPTS.md
  • README.md
  • docker-compose.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • docker-compose.yaml
  • README.md
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: test
  • GitHub Check: bench-smoke
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 7-7: [UnorderedKey] The BITCOIN_RS_RPC_PASSWORD key should go before the BITCOIN_RS_RPC_PORT key

(UnorderedKey)

🔍 Remote MCP Context7, Github Grep

Additional review context

  • Public Compose examples commonly use explicit CMD/CMD-SHELL healthchecks with configured interval, timeout, and retries; this supports checking that the proposed RPC healthcheck has appropriate failure timing and command availability.
  • Multi-stage Docker images must explicitly copy runtime artifacts and dependencies into the final stage; examples copy required binaries/libraries separately rather than relying on builder-stage contents.
  • A common unprivileged-container pattern is to create the system user, create /data, assign ownership, then switch to USER before runtime. This is relevant for verifying named-volume writability on first startup.
  • The Bitcoin Core documentation library was identified, but its documentation query failed due to an upstream connection error; no additional Bitcoin RPC facts were retrieved.

Comment thread .env.example Outdated
# JSON-RPC is published to 127.0.0.1 on the Docker host.
BITCOIN_RS_RPC_PORT=8332
BITCOIN_RS_RPC_USER=bitcoin-rs
BITCOIN_RS_RPC_PASSWORD=password

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  'BITCOIN_RS_RPC_PASSWORD|ports:|8332|rpc' \
  -- .env.example docker-compose.yaml README.md CONCEPTS.md

Repository: gosuda/bitcoin-rs

Length of output: 3709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- compose and example ---'
cat -n docker-compose.yaml | sed -n '1,45p'
cat -n .env.example | sed -n '1,20p'

printf '%s\n' '--- documentation references ---'
rg -n -C 5 'BITCOIN_RS_RPC_PASSWORD|RPC_PASSWORD|RPC authentication|Compose' -- README.md docs CONCEPTS.md 2>/dev/null || true

printf '%s\n' '--- application configuration and validation ---'
rg -n -C 5 'BITCOIN_RS_RPC_PASSWORD|rpc_password|RPC password|rpc.*auth|authentication' -- . \
  -g '!target/**' -g '!node_modules/**' -g '!dist/**' \
  2>/dev/null || true

printf '%s\n' '--- compose interpolation behavior (read-only model) ---'
python3 - <<'PY'
from pathlib import Path
import re

compose = Path("docker-compose.yaml").read_text()
example = Path(".env.example").read_text()

for name, text in [("docker-compose.yaml", compose), (".env.example", example)]:
    print(f"{name}:")
    for i, line in enumerate(text.splitlines(), 1):
        if "BITCOIN_RS_RPC_PASSWORD" in line or "8332" in line:
            print(f"{i}: {line}")

m = re.search(r'BITCOIN_RS_RPC_PASSWORD:\s*"\$\{BITCOIN_RS_RPC_PASSWORD:-([^}]*)\}"', compose)
print("compose_default_password:", m.group(1) if m else "<not found>")
print("example_password:", next(
    (line.split("=", 1)[1] for line in example.splitlines()
     if line.startswith("BITCOIN_RS_RPC_PASSWORD=")), "<not found>"
))
print("rpc_bound_to_loopback:", '"127.0.0.1:${BITCOIN_RS_RPC_PORT:-8332}:8332"' in compose)
PY

Repository: gosuda/bitcoin-rs

Length of output: 47166


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- configuration structure ---'
ast-grep outline crates/node/src/config.rs --lang rust 2>/dev/null || true
ast-grep outline crates/node/src/run.rs --lang rust 2>/dev/null || true

printf '%s\n' '--- default and validation paths ---'
rg -n -C 8 \
  'DEFAULT_RPC_PASSWORD|impl Default for Auth|fn default_for_network|validate|rpc_auth|rpc_password|Config::load|Config::from|build_rpc_auth' \
  crates/node/src crates/node/tests src 2>/dev/null || true

printf '%s\n' '--- container entrypoint and command ---'
fd -i 'Dockerfile|entrypoint|compose' . --type f --exclude target --exclude node_modules \
  -x sh -c 'echo "--- $1"; cat -n "$1"' sh {} \;

printf '%s\n' '--- direct credential acceptance model ---'
python3 - <<'PY'
from pathlib import Path
import re

compose = Path("docker-compose.yaml").read_text()
config = Path("crates/node/src/config.rs").read_text()
example = Path(".env.example").read_text()
readme = Path("README.md").read_text()
concepts = Path("CONCEPTS.md").read_text()

def first(pattern, text, label):
    m = re.search(pattern, text, re.S)
    print(f"{label}: {m.group(1) if m else '<not found>'}")

first(r'const DEFAULT_RPC_PASSWORD: &str = "([^"]+)"', config,
      "application_default_rpc_password")
first(r'BITCOIN_RS_RPC_PASSWORD:\s*"\$\{BITCOIN_RS_RPC_PASSWORD:-([^}]*)\}"',
      compose, "compose_default_rpc_password")
first(r'^BITCOIN_RS_RPC_PASSWORD=(.*)$', example, "example_rpc_password")
print("readme_requires_edit:", "Edit .env and set BITCOIN_RS_RPC_PASSWORD before starting the node." in readme)
print("concepts_claims_explicit_non_empty:", "requires an explicit non-empty RPC password" in concepts)
print("compose_loopback_rpc_publish:",
      '127.0.0.1:${BITCOIN_RS_RPC_PORT:-8332}:8332' in compose)
PY

Repository: gosuda/bitcoin-rs

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- auth defaults and validation ---'
cat -n crates/node/src/config.rs | sed -n '1,75p'
cat -n crates/node/src/config.rs | sed -n '219,280p'
cat -n crates/node/src/config.rs | sed -n '390,470p'
cat -n crates/node/src/config.rs | sed -n '625,735p'

printf '%s\n' '--- container startup ---'
for f in Dockerfile docker-entrypoint.sh docker/start.sh; do
  if [ -f "$f" ]; then
    echo "--- $f"
    cat -n "$f"
  fi
done
rg -n -C 4 'CMD|ENTRYPOINT|bitcoin-rs|BITCOIN_RS_RPC_PASSWORD' Dockerfile .dockerignore docker-compose.yaml 2>/dev/null || true

printf '%s\n' '--- focused source facts ---'
python3 - <<'PY'
from pathlib import Path
import re

text = Path("crates/node/src/config.rs").read_text()
for label, pattern in [
    ("default_password", r'const DEFAULT_RPC_PASSWORD: &str = "([^"]+)"'),
    ("default_auth", r'impl Default for Auth\s*\{.*?Self::basic\([^;]+;',),
    ("validate_body", r'pub fn validate\(&self\) -> Result<\(\)> \{(.*?)\n    \}',),
]:
    m = re.search(pattern, text, re.S)
    print(f"{label}:")
    print(m.group(0) if m else "<not found>")
PY

Repository: gosuda/bitcoin-rs

Length of output: 32011


Remove the default RPC credentials.

Compose and the application accept built-in passwords when BITCOIN_RS_RPC_PASSWORD is unset. Reject empty and placeholder credentials at startup, and require an operator-supplied secret.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 7-7: [UnorderedKey] The BITCOIN_RS_RPC_PASSWORD key should go before the BITCOIN_RS_RPC_PORT key

(UnorderedKey)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example at line 7, Remove the placeholder value from
BITCOIN_RS_RPC_PASSWORD in the environment template, and update startup
validation in the application and Compose configuration to reject unset, empty,
or placeholder credentials, requiring an operator-supplied secret.

Comment thread CONCEPTS.md
The standard node operational configuration tuned for mainnet sync: `fjall` storage backend, multi-peer block download active (outbound peer target 8, pending block budget 128, 16 in-flight requests per peer), hash-pinned assume-valid active on mainnet (height 938343), 450 MiB database cache (`dbcache`, matching Bitcoin Core parity), with secondary indexes (`txindex`, `blockfilterindex`), pruning, and `utreexo` stateless validation disabled by default.

### Container deployment posture
The checked-in Docker Compose specialization of the optimized default posture. The image compiles only the production `fjall` storage and `bitcoinkernel` verifier features and runs as an unprivileged user. Compose publishes P2P on the configured host port, keeps JSON-RPC on the host loopback interface, requires an explicit non-empty RPC password, and gives every selected Bitcoin network its own named data volume so incompatible checkpoints are never reused across a network switch. Shutdown allows up to 5 minutes because the bounded subsystem drain is followed by an unbounded, synchronous full-UTXO clean checkpoint; this is an operational SIGKILL guard, not a checkpoint-duration guarantee.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(docker-compose\.yaml|CONCEPTS\.md|.*\.(rs|yaml|yml))$' | head -200

printf '%s\n' '--- Compose and concept excerpts ---'
for f in docker-compose.yaml CONCEPTS.md; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat -n "$f" | sed -n '1,90p'
  fi
done

printf '%s\n' '--- shutdown/checkpoint references ---'
rg -n -i --glob '*.rs' \
  'shutdown|grace|checkpoint|full.?utxo|clean|drain|SIGKILL|stop_grace|rpc_password|RPC_PASSWORD' . \
  | head -300

Repository: gosuda/bitcoin-rs

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu

for f in crates/node/src/run.rs crates/node/src/event_loop.rs crates/node/src/shutdown.rs crates/node/src/signal.rs crates/node/src/state.rs crates/node/src/config.rs crates/rpc/src/auth.rs crates/rpc/src/server.rs; do
  if [ -f "$f" ]; then
    echo "### $f"
    ast-grep outline "$f" | rg -i 'shutdown|signal|checkpoint|run|drain|rpc|password|auth|config|fn ' | head -120
  fi
done

printf '%s\n' '--- run.rs shutdown orchestration ---'
rg -n -C 12 -i 'shutdown|checkpoint|drain|event_loop|write_clean' crates/node/src/run.rs

printf '%s\n' '--- signal and shutdown modules ---'
cat -n crates/node/src/shutdown.rs | sed -n '1,260p'
cat -n crates/node/src/signal.rs | sed -n '1,240p'

printf '%s\n' '--- checkpoint writer and shutdown entry points ---'
rg -n -C 16 'write_clean_checkpoint|full.?utxo|checkpoint' crates/node/src/state.rs crates/node/src/run.rs crates/node/src/checkpoint.rs

printf '%s\n' '--- RPC password validation/authentication ---'
rg -n -C 12 -i 'rpc_password|password|empty|constant.?time|authenticate|authorization|basic' crates/node/src/config.rs crates/node/src/bitcoin_conf_compat.rs crates/rpc/src/auth.rs crates/rpc/src/server.rs

Repository: gosuda/bitcoin-rs

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- run.rs structure and shutdown references ---'
rg -n '^(pub )?(async )?fn |^struct |^enum |shutdown|checkpoint|drain|join|event_loop|write_clean' crates/node/src/run.rs
printf '%s\n' '--- run.rs focused source ---'
cat -n crates/node/src/run.rs | sed -n '1,280p'

printf '%s\n' '--- event loop focused source ---'
cat -n crates/node/src/event_loop.rs | sed -n '1,150p'

printf '%s\n' '--- signal/shutdown references ---'
rg -n '^(pub )?(fn|struct|enum)|mark_draining|notify_drained|wait|deadline|shutdown' crates/node/src/shutdown.rs crates/node/src/signal.rs
cat -n crates/node/src/shutdown.rs | sed -n '1,220p'
cat -n crates/node/src/signal.rs | sed -n '1,180p'

printf '%s\n' '--- RPC/config focused references ---'
rg -n -C 8 'rpc_password|rpc_user|password|empty|Rpc|rpc' crates/node/src/config.rs crates/node/src/bitcoin_conf_compat.rs crates/rpc/src/auth.rs crates/rpc/src/server.rs | head -240

printf '%s\n' '--- checkpoint API call sites outside checkpoint implementation ---'
rg -n -C 10 'write_clean_checkpoint|write_checkpoint_from_dir|CheckpointWrite' crates/node/src --glob '*.rs'

Repository: gosuda/bitcoin-rs

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- exact shutdown sequence ---'
cat -n crates/node/src/run.rs | sed -n '545,745p'

printf '%s\n' '--- exact checkpoint implementation ---'
cat -n crates/node/src/state.rs | sed -n '1208,1250p'
cat -n crates/node/src/checkpoint.rs | sed -n '818,936p'

printf '%s\n' '--- auth defaults and construction ---'
cat -n crates/node/src/config.rs | sed -n '1,75p'
cat -n crates/rpc/src/auth.rs | sed -n '1,180p'

printf '%s\n' '--- read-only behavioral verifier ---'
python3 - <<'PY'
from pathlib import Path
import re

compose = Path("docker-compose.yaml").read_text()
config = Path("crates/node/src/config.rs").read_text()
run = Path("crates/node/src/run.rs").read_text()
state = Path("crates/node/src/state.rs").read_text()

expr = re.search(
    r'BITCOIN_RS_RPC_PASSWORD:\s*"\$\{BITCOIN_RS_RPC_PASSWORD:-([^}]*)\}"',
    compose,
)
assert expr, "Compose RPC password fallback not found"
fallback = expr.group(1)

def compose_password(env):
    value = env.get("BITCOIN_RS_RPC_PASSWORD")
    return fallback if value is None or value == "" else value

assert compose_password({}) == "password"
assert compose_password({"BITCOIN_RS_RPC_PASSWORD": ""}) == "password"
assert compose_password({"BITCOIN_RS_RPC_PASSWORD": "secret"}) == "secret"

assert "password: String" in config
assert "Auth::basic(user.clone(), password)" in config
assert not re.search(r'password\.trim\(\)\.is_empty\(\)|password\.is_empty\(\)', config)

deadline = re.search(
    r'const DRAIN_DEADLINE:\s*Duration\s*=\s*Duration::from_secs\((\d+)\)',
    run,
)
assert deadline and deadline.group(1) == "5"
assert "shutdown::drain_and_shutdown(DRAIN_DEADLINE);" in run
assert "let clean_checkpoint = state.write_clean_checkpoint();" in run
assert "&self.utxo" in state
print("compose_unset_password=password")
print("compose_empty_password=password")
print("compose_explicit_password=preserved")
print("empty_password_validation=absent")
print("subsystem_drain_deadline_seconds=5")
print("checkpoint_after_drain=synchronous_write_clean_checkpoint")
print("checkpoint_receives=UTXO_set_and_applied_tip")
PY

Repository: gosuda/bitcoin-rs

Length of output: 26866


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

compose = Path("docker-compose.yaml").read_text()
run = Path("crates/node/src/run.rs").read_text()
state = Path("crates/node/src/state.rs").read_text()

match = re.search(
    r'BITCOIN_RS_RPC_PASSWORD:\s*"\$\{BITCOIN_RS_RPC_PASSWORD:-([^}]*)\}"',
    compose,
)
assert match, "BITCOIN_RS_RPC_PASSWORD fallback is missing"
fallback = match.group(1)

def compose_password(value):
    return fallback if value in (None, "") else value

for value, expected in [(None, "password"), ("", "password"), ("secret", "secret")]:
    actual = compose_password(value)
    assert actual == expected, (value, actual, expected)
    print(f"compose_password({value!r})={actual!r}")

drain = run.index("shutdown::drain_and_shutdown(DRAIN_DEADLINE);")
checkpoint = run.index("let clean_checkpoint = state.write_clean_checkpoint();")
assert drain < checkpoint
assert "const DRAIN_DEADLINE: Duration = Duration::from_secs(5);" in run
assert "&self.utxo" in state
print("shutdown_order=bounded_drain_then_synchronous_checkpoint")
print("drain_deadline_seconds=5")
print("checkpoint_source=UTXO_set")
PY

Repository: gosuda/bitcoin-rs

Length of output: 363


Make BITCOIN_RS_RPC_PASSWORD mandatory.

docker-compose.yaml:16 substitutes password when the variable is unset or empty. This contradicts CONCEPTS.md:33 and exposes RPC through a predictable credential. Use ${BITCOIN_RS_RPC_PASSWORD:?BITCOIN_RS_RPC_PASSWORD must be set} or equivalent secret injection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONCEPTS.md` at line 33, Update the Docker Compose RPC password configuration
referenced by the concepts description so BITCOIN_RS_RPC_PASSWORD is required
and non-empty, removing the current fallback to a predictable password. Use
Compose’s required-variable syntax or equivalent secret injection while
preserving the existing RPC configuration.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 \
  'SIGTERM|SIGINT|shutdown|checkpoint|clean|UTXO|full.?UTXO' \
  --glob '*.rs' \
  --glob 'docker-compose.yaml' .

Repository: gosuda/bitcoin-rs

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- compose ---'
sed -n '1,80p' docker-compose.yaml

printf '%s\n' '--- signal/shutdown/checkpoint definitions ---'
rg -n \
  '(^|[[:space:]])(fn|async fn|struct|enum)[[:space:]]+[A-Za-z0-9_]*(shutdown|signal|checkpoint|clean|utxo)[A-Za-z0-9_]*|SIGTERM|SIGINT|ctrl_c|stop_grace_period|BITCOIN_RS_RPC_PASSWORD|RPC_PASSWORD|write_checkpoint|checkpoint' \
  --glob '*.rs' --glob 'docker-compose.yaml' \
  . | head -n 500

printf '%s\n' '--- likely entrypoints ---'
fd -t f -e rs . | while read -r f; do
  if rg -q 'tokio::signal|signal::|SIGTERM|SIGINT|ctrl_c|shutdown' "$f"; then
    printf '%s\n' "$f"
  fi
done

Repository: gosuda/bitcoin-rs

Length of output: 42950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- compose ---'
sed -n '1,80p' docker-compose.yaml

printf '%s\n' '--- focused shutdown and checkpoint references ---'
rg -n -C 6 \
  'SIGTERM|SIGINT|ctrl_c|shutdown|stop_grace_period|BITCOIN_RS_RPC_PASSWORD|RPC_PASSWORD|write_checkpoint|checkpoint' \
  --glob '*.rs' --glob 'docker-compose.yaml' . | head -n 600

printf '%s\n' '--- files containing signal handling ---'
fd -t f -e rs . | while read -r f; do
  rg -q 'tokio::signal|signal::|SIGTERM|SIGINT|ctrl_c|shutdown' "$f" && printf '%s\n' "$f"
done

Repository: gosuda/bitcoin-rs

Length of output: 45136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- run shutdown path ---'
sed -n '640,750p' crates/node/src/run.rs
sed -n '820,925p' crates/node/src/run.rs

printf '%s\n' '--- shutdown and signal modules ---'
cat -n crates/node/src/shutdown.rs
cat -n crates/node/src/signal.rs
sed -n '1,140p' bin/bitcoin-rs/src/main.rs

printf '%s\n' '--- state checkpoint entrypoint ---'
sed -n '1190,1260p' crates/node/src/state.rs

printf '%s\n' '--- checkpoint writer ---'
sed -n '819,1065p' crates/node/src/checkpoint.rs

printf '%s\n' '--- startup checkpoint recovery ---'
sed -n '900,1085p' crates/node/src/state.rs

Repository: gosuda/bitcoin-rs

Length of output: 32595


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- UTXO snapshot writer and iterator ---'
rg -n -C 12 \
  'write_snapshot_observed|write_snapshot|iter\(|stable_view|record_count|SnapshotWrite' \
  crates/utxo crates/node/src/checkpoint.rs | head -n 500

printf '%s\n' '--- shutdown deadlines and run setup ---'
rg -n -C 8 \
  'DRAIN_DEADLINE|BOOTSTRAP_JOIN_DEADLINE|with_shutdown_receiver|install_shutdown_handler|shutdown_rx|shutdown_tx' \
  crates/node/src/run.rs crates/node/src/config.rs bin/bitcoin-rs/src/main.rs

printf '%s\n' '--- checkpoint interruption and recovery tests ---'
rg -n -C 10 \
  'failpoint|CURRENT|staging|interrupted|crash|old.*current|current.*old|preserv|quarantine|resync' \
  crates/node/src/checkpoint.rs crates/node/src/state.rs crates/node/src/run.rs crates/node/tests

Repository: gosuda/bitcoin-rs

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=$(rg -l 'fn write_snapshot_observed' crates/utxo --glob '*.rs' | head -n 1)
printf 'snapshot_file=%s\n' "$file"
line=$(rg -n 'fn write_snapshot_observed' "$file" | head -n 1 | cut -d: -f1)
start=$((line-35))
[ "$start" -lt 1 ] && start=1
end=$((line+180))
sed -n "${start},${end}p" "$file"

printf '%s\n' '--- UTXO stable-view API ---'
rg -n -C 20 \
  'pub fn with_stable_view|fn with_stable_view|pub fn record_count|fn record_count|pub struct UtxoSet|impl UtxoSet' \
  crates/utxo/src --glob '*.rs' | head -n 350

Repository: gosuda/bitcoin-rs

Length of output: 25733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

compose = Path("docker-compose.yaml").read_text()
run = Path("crates/node/src/run.rs").read_text()
state = Path("crates/node/src/state.rs").read_text()
checkpoint = Path("crates/node/src/checkpoint.rs").read_text()
snapshot = Path("crates/utxo/src/snapshot.rs").read_text()

assert 'stop_grace_period: 5m' in compose
assert 'BITCOIN_RS_RPC_PASSWORD: "${BITCOIN_RS_RPC_PASSWORD:-password}"' in compose
assert '"127.0.0.1:${BITCOIN_RS_RPC_PORT:-8332}:8332"' in compose

assert 'const DRAIN_DEADLINE: Duration = Duration::from_secs(5);' in run
assert 'state.write_clean_checkpoint()' in run
drain = run.index('shutdown::drain_and_shutdown(DRAIN_DEADLINE)?;')
checkpoint_call = run.index('let clean_checkpoint = state.write_clean_checkpoint();')
bootstrap_join = run.index('let deadline = std::time::Instant::now() + BOOTSTRAP_JOIN_DEADLINE;')
assert drain < checkpoint_call < bootstrap_join

entry = state.index('pub(crate) fn write_clean_checkpoint(')
writer_call = state.index('crate::checkpoint::write_checkpoint_from_dir(', entry)
assert writer_call > entry

snapshot_call = checkpoint.index('write_snapshot_observed(')
assert '&mut utxo_writer' in checkpoint[snapshot_call:snapshot_call + 500]
assert 'sync_file(&utxo_file, failpoint, CheckpointFailpoint::UtxoSync)?;' in checkpoint

body = snapshot[snapshot.index('pub fn write_snapshot_observed'):snapshot.index('pub fn write_snapshot_observed') + 2500]
assert 'set.with_stable_view' in body
assert 'for shard_idx in 0_u8..=u8::MAX' in body
assert 'for record in &table.table' in body
assert 'for output in record.outputs()' in body

utxo_sync = checkpoint.index('sync_file(&utxo_file')
current_rename = checkpoint.index('rename_current(', utxo_sync)
assert utxo_sync < current_rename

print("compose_fallback=password")
print("rpc_bind=loopback")
print("subsystem_drain=5s")
print("checkpoint= synchronous_full_utxo_traversal_without_internal_deadline")
print("publication=UTXO_sync_before_CURRENT_rename")
PY

Repository: gosuda/bitcoin-rs

Length of output: 332


Fix the RPC password claim.

  • Compose defaults BITCOIN_RS_RPC_PASSWORD to "password"; it does not require an explicit password. Remove the fallback or document its security impact.
  • The shutdown description is accurate. The node drains subsystems for 5 seconds, then performs a synchronous full-UTXO checkpoint without an internal timeout. stop_grace_period: 5m is only the outer SIGKILL deadline. Atomic CURRENT publication preserves the previous checkpoint if SIGKILL interrupts the write.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONCEPTS.md` at line 33, Update the Compose specialization description to
accurately state that BITCOIN_RS_RPC_PASSWORD defaults to "password" rather than
requiring an explicit non-empty password, and document the security impact of
this fallback if retaining it. Leave the existing shutdown behavior description
unchanged.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82c1030ef0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker-compose.yaml
BITCOIN_RS_NETWORK: "${BITCOIN_RS_NETWORK:-mainnet}"
BITCOIN_RS_STORAGE_BACKEND: fjall
BITCOIN_RS_RPC_USER: "${BITCOIN_RS_RPC_USER:-bitcoin-rs}"
BITCOIN_RS_RPC_PASSWORD: "${BITCOIN_RS_RPC_PASSWORD:-password}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject the shipped fallback RPC password

The fresh revision replaces the formerly blank sample with the literal password and also uses it as the :-password fallback, so copying .env.example unchanged—or starting without .env—still launches RPC with publicly known credentials instead of requiring the explicit secret promised by README and CONCEPTS.md. On a multi-user Docker host, another local user can reach the loopback-published port and authenticate; make Compose fail when the password is unset, empty, or still the placeholder.

AGENTS.md reference: AGENTS.md:L5-L7

Useful? React with 👍 / 👎.

@gosunuts
gosunuts merged commit 4d3fd2f into main Aug 11, 2026
12 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants