feat(docker): add Docker support with Dockerfile and docker-compose c… - #48
Conversation
|
Warning Review limit reached
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 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. 📝 WalkthroughSummary by CodeRabbit
WalkthroughThe 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. ChangesDocker deployment
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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches✨ Simplify code
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
.dockerignore.env.example.gitignoreDockerfileREADME.mddocker-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
/datais both the mounted path and writable by the unprivileged runtime user. EXPOSEis metadata only; actual publishing comes from Composeports. Binding RPC to127.0.0.1restricts 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
getblockchaininfoexposes synchronization-related fields such asverificationprogressandinitialblockdownload; 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
| 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}" |
There was a problem hiding this comment.
🔒 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
|
@codex review |
There was a problem hiding this comment.
💡 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".
| 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. |
There was a problem hiding this comment.
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 👍 / 👎.
| 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/ |
There was a problem hiding this comment.
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 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
.env.exampleCONCEPTS.mdREADME.mddocker-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-SHELLhealthchecks with configuredinterval,timeout, andretries; 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 toUSERbefore 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.
| # 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 |
There was a problem hiding this comment.
🔒 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.mdRepository: 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)
PYRepository: 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)
PYRepository: 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>")
PYRepository: 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.
| 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. |
There was a problem hiding this comment.
🔒 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 -300Repository: 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.rsRepository: 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")
PYRepository: 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")
PYRepository: 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
doneRepository: 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"
doneRepository: 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.rsRepository: 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/testsRepository: 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 350Repository: 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")
PYRepository: gosuda/bitcoin-rs
Length of output: 332
Fix the RPC password claim.
- Compose defaults
BITCOIN_RS_RPC_PASSWORDto"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: 5mis only the outer SIGKILL deadline. AtomicCURRENTpublication 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.
There was a problem hiding this comment.
💡 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".
| 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}" |
There was a problem hiding this comment.
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 👍 / 👎.
…onfiguration