Skip to content

darepod: add client daemon scaffold with actor system - #123

Merged
Roasbeef merged 13 commits into
mainfrom
darepod-daemon-scaffold
Feb 26, 2026
Merged

darepod: add client daemon scaffold with actor system#123
Roasbeef merged 13 commits into
mainfrom
darepod-daemon-scaffold

Conversation

@Roasbeef

Copy link
Copy Markdown
Member

In this PR, we add the initial darepod client daemon -- the main entry
point that boots up, connects to lnd and the ark server, initializes the
actor system, and exposes a gRPC API for external tooling (CLI, GUI, etc).

The daemon follows the same lifecycle pattern used by lnd and tapd: cobra
for CLI parsing, viper for config/env overrides, lndclient for the lnd
connection (blocking until chain-synced and wallet-unlocked), and a signal
interceptor for graceful shutdown. The startup flow is sequential and
numbered for readability -- connect lnd, dial the ark server, init the
actor system, start the chain source, then fire up the gRPC listener.

Indexer RPC Schema and Client

The first four commits (carried over from indexer-draft-oor-lite) add the
arkrpc proto definitions for the indexer service and a mailbox-backed Go
client in indexer/. The indexer client takes an RPCClient interface
(satisfied by the serverconn UnaryFacade) and wraps the generated
IndexerServiceMailboxClient stubs. We also add taproot script-scope auth
helpers for proof-of-control signing, and a cursor-based sync client with
full test coverage.

Daemon Scaffold

In cmd/darepod/main.go, we wire up cobra with flags for --network,
--lnd.host, --lnd.tlspath, --lnd.macaroonpath, --server.host,
--server.insecure, --rpc.listenaddr, and the mailbox ID pair
(--server.localmailboxid, --server.remotemailboxid). Config structs
live in darepod/config.go with sensible defaults and a Validate()
method.

darepod/server.go is the main orchestrator. The RunUntilShutdown method
walks through the numbered startup steps:

  1. Connect to lnd via lndclient.NewLndServices
  2. Dial the ark server's mailbox edge (with TLS modes: custom cert, system
    pool, or insecure for regtest)
  3. Initialize actor.NewActorSystem() with deferred graceful shutdown
  4. Create the chain backend via chainbackends.NewLNDBackendFromLndClient
    and register a ChainSourceActor under chainsource.ChainSourceKey
  5. Placeholder for the serverconn Runtime (blocked on DeliveryStore,
    a.k.a the DB layer)
  6. Start the gRPC server with DaemonService registered on both local
    gRPC and a mailbox ServeMux for dual-transport access
  7. Block until shutdown

DaemonService RPC

We add a daemonrpc/daemon.proto with a single GetInfo RPC that returns
version, commit hash, network, lnd pubkey, lnd alias, and current block
height (fetched via ChainKit.GetBestBlock). The RPCServer implements
both the gRPC server interface and the DaemonServiceMailboxServer
interface, so the same handler serves both transports. The mailbox stubs
are generated by protoc-gen-mailboxrpc as part of make rpc.

What's Left

The serverconn Runtime needs a persistent DeliveryStore before it can
be wired -- that's the DB layer work. Once that lands, the mailbox ServeMux
plugs into ConnectorConfig.Dispatchers and the indexer client gets
initialized via indexer.New(runtime.Unary(), ...). The remaining actors
(round, OOR, VTXO, wallet) similarly depend on the store.

See each commit message for a detailed description w.r.t the incremental
changes.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Roasbeef, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the initial scaffold for the darepod client daemon, a crucial component for interacting with the Ark protocol. It establishes the core infrastructure for connecting to an lnd node and an Ark operator server, managing an actor system for internal logic, and exposing a gRPC API for external control. The changes also include the necessary RPC definitions and client implementations for the Ark indexer service, enabling wallet operations and event synchronization. This foundational work sets the stage for future development of Ark wallet functionalities.

Highlights

  • Initial darepod Client Daemon: Introduces the foundational darepod client daemon, designed to connect to an lnd node and an Ark operator server, and expose a gRPC API for external tools.
  • Indexer RPC Schema & Client: Adds arkrpc protobuf definitions for the indexer service, including VTXO statuses, event types, and methods for script registration, VTXO listing, subtree retrieval, and event feeds. A mailbox-backed Go client (indexer/client.go) is provided, along with taproot script-scope authentication helpers and a cursor-based sync client.
  • Daemon Scaffolding & Configuration: Implements the daemon's main entry point (cmd/darepod/main.go) using Cobra for CLI parsing and Viper for configuration. Configuration structures (darepod/config.go) are defined with sensible defaults and validation.
  • Core Daemon Orchestration: The darepod/server.go file establishes the main orchestration logic, detailing a sequential startup flow that includes connecting to lnd, dialing the Ark server, initializing an actor system, setting up a chain source actor, and starting the daemon's gRPC server.
  • Daemon's Own gRPC API: Defines a DaemonService (daemonrpc/daemon.proto) with a GetInfo RPC, allowing external clients to query the daemon's status, version, network, and lnd connection details. This service is accessible via both standard gRPC and mailbox RPC.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Makefile
    • Updated build, install, and clean targets to include the new darepod binary.
  • arkrpc/indexer.pb.go
    • Added generated Go code for the IndexerService protobuf definitions.
  • arkrpc/indexer.proto
    • Added new protobuf definitions for the IndexerService, including VTXO lifecycle statuses, event types, and RPC methods for script registration, VTXO queries, and event feeds.
  • arkrpc/indexer_grpc.pb.go
    • Added generated gRPC client and server interfaces for the IndexerService.
  • arkrpc/indexer_mailboxrpc.pb.go
    • Added generated mailbox RPC client and server interfaces for the IndexerService.
  • cmd/darepod/main.go
    • Added the main function and Cobra command setup for the darepod daemon, including flag parsing and configuration loading.
  • daemonrpc/daemon.pb.go
    • Added generated Go code for the DaemonService protobuf definitions.
  • daemonrpc/daemon.proto
    • Added new protobuf definitions for the DaemonService, including the GetInfo RPC.
  • daemonrpc/daemon_grpc.pb.go
    • Added generated gRPC client and server interfaces for the DaemonService.
  • daemonrpc/daemon_mailboxrpc.pb.go
    • Added generated mailbox RPC client and server interfaces for the DaemonService.
  • darepod/config.go
    • Added configuration structures for the darepod daemon, including settings for lnd, Ark server, and RPC, along with default values and validation logic.
  • darepod/log.go
    • Added logging setup for the darepod package.
  • darepod/rpc_server.go
    • Added implementation of the DaemonService gRPC interface, providing daemon information via GetInfo.
  • darepod/server.go
    • Added the main server orchestration logic, handling daemon startup, lnd and Ark server connections, actor system initialization, and gRPC server management.
  • go.mod
    • Updated to include new dependencies: github.com/spf13/cobra and github.com/spf13/viper.
  • go.sum
    • Updated dependency checksums.
  • indexer/client.go
    • Added a client wrapper for the IndexerServiceMailboxClient, facilitating proof-of-control signing for various indexer RPC calls.
  • indexer/nonce.go
    • Added a utility function for generating random hexadecimal nonces.
  • indexer/sync_client.go
    • Added a SyncClient for managing cursor-based synchronization with indexer event feeds, along with an in-memory cursor store.
  • indexer/sync_client_test.go
    • Added unit tests for the SyncClient and MemorySyncCursorStore.
  • scripts/gen_protos.sh
    • Modified the script to include daemonrpc in the protobuf generation process.
Activity
  • The pull request introduces the initial scaffold for the darepod client daemon.
  • It includes the integration of arkrpc and daemonrpc protobuf definitions and their generated Go, gRPC, and mailbox RPC clients/servers.
  • The daemon's lifecycle management, including lnd and Ark server connections, actor system, and chain source, has been set up.
  • Configuration handling via Cobra and Viper has been implemented.
  • A basic GetInfo RPC endpoint is available for daemon status queries.
  • The serverconn.Runtime is noted as a placeholder, awaiting the database layer for persistent DeliveryStore.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a significant amount of new functionality by scaffolding the darepod client daemon. The changes are well-structured, including a new darepod main package, configuration management with Cobra and Viper, RPC definitions for daemon and indexer services, and the core server logic with an actor system. The code quality is high, with clear separation of concerns and good use of interfaces for testability.

I've found a couple of issues. A redundant command in the Makefile's clean target, and a potential nil pointer dereference in indexer/sync_client.go due to incorrect nil checks, which I've flagged as high severity.

Comment thread indexer/sync_client.go Outdated
Comment on lines +81 to +86
if c == nil || c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The check c == nil || c.backend == nil will cause a panic if c is nil due to the access to c.backend. The checks for c being nil and its fields being nil should be separated to prevent this panic.

Suggested change
if c == nil || c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}
if c == nil {
return nil, fmt.Errorf("SyncClient is nil")
}
if c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}

Comment thread indexer/sync_client.go Outdated
Comment on lines +124 to +129
if c == nil || c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Similar to the other sync method, the check c == nil || c.backend == nil will panic if c is nil. The checks should be separated to handle the nil receiver case gracefully before accessing its fields.

Suggested change
if c == nil || c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}
if c == nil {
return nil, fmt.Errorf("SyncClient is nil")
}
if c.backend == nil {
return nil, fmt.Errorf("missing sync backend")
}
if c.cursors == nil {
return nil, fmt.Errorf("missing sync cursor store")
}

Comment thread Makefile Outdated
clean: #? Remove build artifacts
@$(call print, "Cleaning build artifacts.")
$(RM) ./merge-sql-schemas
$(RM) -r ./bin/darepod

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This line is redundant because the next command, $(RM) -r ./bin, removes the entire ./bin directory, which includes the darepod binary. This line can be safely removed.

@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from bf71d8c to d5de844 Compare February 22, 2026 20:05
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

https://github.com/lightninglabs/darepo-client/blob/bf71d8cffa56cb4ad2e29ed0f0a8ad0bee38d1c1/darepod/config.go#L200
P1 Badge Fix tilde expansion for default data directory

When DataDir is the default "~/.darepod", path[1:] starts with /, so filepath.Join(home, path[1:]) discards home and resolves to "/.darepod". That sends all default state/log paths to the filesystem root, which will fail for normal users and breaks out-of-the-box daemon startup unless run as root.


https://github.com/lightninglabs/darepo-client/blob/bf71d8cffa56cb4ad2e29ed0f0a8ad0bee38d1c1/indexer/client.go#L355
P2 Badge Preserve unset expiry in receive-script registration

expires_at_unix_s is documented as optional with 0 meaning “use server default,” but this assignment converts expiresAt.Unix() directly to uint64; passing Go’s zero time.Time{} produces a negative Unix timestamp that wraps to a huge unsigned value. That can silently send an effectively never-expiring timestamp instead of the intended unset/default behavior.


https://github.com/lightninglabs/darepo-client/blob/bf71d8cffa56cb4ad2e29ed0f0a8ad0bee38d1c1/cmd/darepod/main.go#L103-L106
P2 Badge Wire environment overrides into runtime config

This enables viper.AutomaticEnv(), but the daemon never reads values back from viper or binds flags/config keys, and run uses the in-memory cfg struct populated only by Cobra flag vars. As a result, environment variables (for example DAREPOD_NETWORK in containerized deployments) do not actually override configuration.

ℹ️ 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".

@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review this

@claude

claude Bot commented Feb 23, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 3m 27s —— View job


Code Review

Good overall structure — the numbered startup steps, lifecycle pattern mirroring lnd/tapd, and the clean separation between Server (orchestration) and RPCServer (handler) all read well. The actor system wiring and chain source registration are clear. Comments below range from a correctness issue to style nits.


Bugs / Correctness

1. Viper env var overrides broken for nested config keyscmd/darepod/main.go:98-100

v.AutomaticEnv() maps env vars to viper keys by upcasing and prepending the prefix. For key lnd.host it looks for DAREPOD_LND.HOST — a literal dot in the env var name, which most shells reject. Users expecting DAREPOD_LND_HOST to work would be surprised. Top-level keys (DAREPOD_NETWORK) are fine; only the nested dot-separated ones are broken.

Fix: add v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) before AutomaticEnv so lnd.host maps to DAREPOD_LND_HOST. Fix this →

2. Zero time.Time{} produces a huge expires_at_unix_s valueindexer/client.go:355

ExpiresAtUnixS: uint64(expiresAt.Unix()),

The proto comment says 0 means "use server default", but if a caller passes time.Time{}, expiresAt.Unix() returns a large negative number (year 1 CE), and the cast to uint64 wraps it to ~18.4×10¹⁸. The guard should be:

var expiresAtUnixS uint64
if !expiresAt.IsZero() {
    expiresAtUnixS = uint64(expiresAt.Unix())
}
ExpiresAtUnixS: expiresAtUnixS,

3. RPCConfig.TLSCertPath / TLSKeyPath are never useddarepod/server.go:193

The config struct exposes TLS paths for the daemon's own gRPC listener, but grpc.NewServer() is called without credentials. The TLS fields are dead config at the moment. Either wire them up or drop the fields (and a TODO comment is needed so reviewers aren't left wondering).


False Positives in Prior Review Comments

  • Codex P1 (tilde expansion): Not a bug in Go. filepath.Join("/home/user", "/.darepod") produces Clean("/home/user//.darepod") = /home/user/.darepod. Go's filepath.Join concatenates and cleans; it does not treat a leading / in a non-first argument as an absolute root like Python's os.path.join.

  • Gemini nil pointer on c == nil || c.backend == nil: Also not a bug. Go's || is short-circuit: c.backend is never evaluated when c == nil. This is valid defensive nil-receiver code.


Minor Issues

4. Makefile clean redundancyMakefile:363

$(RM) -r ./bin/darepod immediately before $(RM) -r ./bin is redundant — the second removes the entire ./bin directory.

5. randomNonceHex missing function commentindexer/nonce.go:9

Per project guidelines every function (including unexported) must have a doc comment starting with the function name. randomNonceHex has none. Also n is never validated against the 32-byte contract expressed by the call sites; a <= 0 guard exists but no assertion that n == registrationNonceBytes.

6. TaprootScriptScope exported fields lack GoDocindexer/client.go:155-156

Per guidelines, exported struct fields need GoDoc starting with the field name:

// PkScript is the P2TR output script.
PkScript   []byte

// SigningKey is the P2TR output key corresponding to PkScript.
SigningKey *btcec.PrivateKey

7. newTaprootScope missing function commentindexer/client.go:159

Unexported functions still require a comment per project style. None present.

8. newMailboxEdge is dead codedarepod/server.go:318

The helper is defined but never called in the current scaffold. Fine to keep as a forward stub, but worth a // TODO comment tying it to the serverconn.Runtime wiring step so it doesn't look forgotten.

9. lnd_alias field ordering in protodaemonrpc/daemon.proto:44

Field 7 (lnd_alias) logically belongs adjacent to field 4 (lnd_identity_pubkey), not after server_connected. Field numbers are stable so the ordering is purely cosmetic, but grouping related lnd fields together would improve readability.

10. gRPC server error log after graceful stopdarepod/server.go:218-221

grpcServer.Serve returns nil on GracefulStop, so the error branch won't fire on clean shutdown. But the ctx passed to the goroutine's log.ErrorS call may already be cancelled at the point of any error. Consider using a fresh background context for that log, or at minimum note the context-cancellation race.


Summary

The scaffold is well-structured and follows the lnd/tapd lifecycle pattern cleanly. The main actionable items before merge are: the env-var key replacer (item 1, silent misconfiguration that will confuse operators), the zero-time wrapping in RegisterReceiveScriptTaproot (item 2), and the missing GoDoc on TaprootScriptScope fields and the randomNonceHex/newTaprootScope comments (items 5–7, required by project guidelines).

@Roasbeef

Copy link
Copy Markdown
Member Author

Addressed the actionable items from the review in 85c49a6:

  1. Env var key replacer — Added v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) so DAREPOD_LND_HOST works for lnd.host. Good catch.

  2. Zero time.Time wrapping — Guarded the expiresAt→uint64 cast so zero times produce 0 (server default) instead of wrapping.

  3. Missing comments — Added GoDoc on TaprootScriptScope fields, function comments on randomNonceHex and newTaprootScope.

  4. Dead code TODOs — Added TODO comments tying newMailboxEdge and the gRPC TLS config fields to the serverconn runtime wiring step.

  5. TLS MinVersion — Already fixed in 033ad08 (set tls.VersionTLS12 on both config paths).

Re item 3 (unused RPCConfig.TLSCertPath/TLSKeyPath): keeping the fields with a TODO — they'll be wired once auto-generated TLS material is in place.

Re item 10 (gRPC error log after graceful stop): grpcServer.Serve returns nil on GracefulStop, so the error branch only fires on actual failures. The cancelled ctx in the log is fine since btclog doesn't inspect context state.

@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch 2 times, most recently from caac373 to fece1d3 Compare February 23, 2026 04:52
@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review this

@claude

claude Bot commented Feb 23, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 2m 8s —— View job


Code Review

The follow-up commit (85c49a6) addressed all the items from the prior review. The code is in good shape overall — the numbered startup steps are clear, lifecycle management is clean, and the actor wiring reads well. Two new issues below from this pass.


Bugs

1. RegisterReceiveScriptTaproot TLV proof still encodes the wrong expiry for zero timeindexer/client.go:452-455

The ExpiresAtUnixS proto field was guarded (good), but the TLV-encoded proof bytes — which are signed and validated by the server — are computed before the guard:

msgBytes, err := encodeRegistrationProofTLV(
    c.serverID, c.principal, pkScript, nonce,
    uint64(now.Unix()), uint64(expiresAt.Unix()),  // ← wraps for zero time
)
// ...
var expiresAtUnixS uint64
if !expiresAt.IsZero() {
    expiresAtUnixS = uint64(expiresAt.Unix())
}

When expiresAt.IsZero(), the TLV message embeds the wrapped huge timestamp while ExpiresAtUnixS is 0. Any server that checks the proof's TLV expiry against the request field will reject this as a mismatch. The fix should derive the safe value before calling encodeRegistrationProofTLV:

var expiresAtUnixS uint64
if !expiresAt.IsZero() {
    expiresAtUnixS = uint64(expiresAt.Unix())
}

msgBytes, err := encodeRegistrationProofTLV(
    c.serverID, c.principal, pkScript, nonce,
    uint64(now.Unix()), expiresAtUnixS,
)

Fix this →

2. Nil dereference if env.Rpc is nil in handleInboundRPCdarepod/server.go:430-438

There's a guard for env.Body == nil but the code accesses env.Rpc.Service, env.Rpc.Method, and env.Rpc.ReplyTo without checking env.Rpc != nil first. A malformed envelope with a nil Rpc field will panic:

if env.Body == nil {          // ← guards Body
    return fmt.Errorf(...)
}
// env.Rpc is not guarded
respMsg, err := s.mailboxMux.ServeRPC(
    ctx, env.Rpc.Service, env.Rpc.Method,  // ← panics if Rpc == nil
    env.Body.Value,
)

Add a corresponding nil check for env.Rpc alongside the env.Body check. Fix this →


Minor

3. Makefile clean redundancy still presentMakefile:363

$(RM) -r ./bin/darepod immediately before $(RM) -r ./bin is still there. The second line removes the entire ./bin directory, making the first line dead. Can drop it.

4. Validate() doesn't check mailbox IDsdarepod/config.go:147-176

LocalMailboxID and RemoteMailboxID are both required for the serverconn runtime to function (step 7 of startup), but Validate() doesn't enforce them. Startup will succeed through steps 1–6 and then fail at step 7 with a less-obvious error. Worth adding to Validate() so the failure is caught early with a clear message.

5. lnd_alias proto field orderingdaemonrpc/daemon.proto:44

Field 7 (lnd_alias) logically belongs with field 4 (lnd_identity_pubkey) since they both describe lnd node identity. Proto field numbers are stable so this is cosmetic only.


Notes on Prior Review Comments

  • Gemini nil panic on c == nil || c.backend == nil: Still not a bug. Go short-circuits ||, so c.backend is never evaluated when c == nil. The current code (sync_client.go:81,124) is correct as written.
  • All items from the previous Claude review are addressed in the 85c49a6 commit.

@Roasbeef
Roasbeef force-pushed the indexer-draft-oor-lite branch from 40d7b4d to e0e7484 Compare February 23, 2026 05:01
@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from fece1d3 to 636a08b Compare February 23, 2026 05:03
@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review this

@claude

claude Bot commented Feb 23, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 3m 15s —— View job


Code Review

  • Gather context from PR and previous reviews
  • Read current state of changed files
  • Analyze for bugs, correctness issues, style violations
  • Post review feedback

All items from the previous two rounds of review have been addressed cleanly. The code is in good shape. A few items on the new lndbackend/client_wallet.go and the expanded darepod/server.go below.


Previously Raised Items

All prior action items are resolved:

  • v.SetEnvKeyReplacer is wired in cmd/darepod/main.go:100
  • expiresAtUnixS zero-guard computed before encodeRegistrationProofTLV and used in both the TLV message and the proto field ✓
  • env.Rpc != nil guard added before handleInboundRPC accesses env.Rpc.Service
  • Mailbox IDs validated in Config.Validate() at darepod/config.go:167-174
  • Makefile clean redundancy removed ✓
  • randomNonce/newTaprootScope function comments and TaprootScriptScope field GoDoc added ✓

New Issues

1. MuSig2CreateSession always serializes pubkeys as compressed (33 bytes)lndbackend/client_wallet.go:132-135

The in-code comment acknowledges that MuSig2Version100RC2+ expects 32-byte x-only keys, but the loop always calls SerializeCompressed():

signerBytes[i] = pk.SerializeCompressed()

If lndclient.MuSig2CreateSession doesn't adapt the key format based on the version parameter, sessions opened with version ≥ 100RC2 will send 33-byte keys to lnd's signer instead of the expected 32-byte x-only format, causing verification failures during VTXO tree signing. The fix is to check the version and call schnorr.SerializePubKey(pk) for RC2+. If lndclient handles this internally, a comment to that effect would remove the ambiguity. Fix this →

2. fetchOperatorTerms parses resp.Pubkey without a length checkdarepod/server.go:671

pubKey, err := btcec.ParsePubKey(resp.Pubkey)

If the server returns an empty or absent Pubkey (e.g., due to a proto version mismatch or a buggy server), btcec.ParsePubKey([]byte{}) returns unexpected EOF — not operator pubkey is missing. Since pubKey is required for the round actor to construct valid boarding scripts, a missing key should be caught with a clear message:

if len(resp.Pubkey) == 0 {
    return nil, fmt.Errorf("operator pubkey is missing")
}
pubKey, err := btcec.ParsePubKey(resp.Pubkey)

Fix this →


Minor

3. buildRPCDispatchers will silently drop new service methods if not registereddarepod/server.go:414-421

The dispatcher map currently has one entry (DaemonService.GetInfo). When indexer/wallet/round service methods are wired, each must be added here manually. There's no mechanism to catch omissions. A TODO noting "add indexer and wallet service methods here once their clients are initialized" would help avoid silent no-ops where the server sends a KIND_REQUEST and the client silently ignores it.

4. Compile-time interface check is partiallndbackend/client_wallet.go:50

var _ input.Signer = (*ClientWallet)(nil)

The comment explains the cycle prevents checking against round.ClientWallet. round.ClientWallet additionally requires DeriveNextKey and several MuSig2* methods. If a future refactor of round.ClientWallet adds or renames a method, compilation won't catch that ClientWallet is out of sync. The existing comment is sufficient; a // NOTE: full round.ClientWallet check omitted to avoid import cycle suffix on the var would make the intent clearer to future readers.

5. lnd_alias proto field orderingdaemonrpc/daemon.proto:44

Field 7 (lnd_alias) is logically an lnd identity field but sits after server_connected (field 6). Grouping it alongside lnd_identity_pubkey (field 4) would improve readability. Proto field numbers are stable; this is cosmetic only.


Summary

The changes in this round (lndbackend/client_wallet.go, expanded darepod/server.go) are well structured. The two actionable items are the MuSig2 pubkey serialization format (item 1, correctness risk during VTXO signing) and the missing resp.Pubkey length guard (item 2, poor diagnostics on protocol mismatch). Items 3–5 are minor.

@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from 636a08b to 558ec50 Compare February 23, 2026 19:57
@Roasbeef

Copy link
Copy Markdown
Member Author

Addressed all actionable items from both Claude reviews, the Codex review, and Gemini comments:

From Claude review #2 (latest):

  1. TLV proof expiry mismatch for zero time — Moved expiresAtUnixS zero-guard before encodeRegistrationProofTLV so both the signed TLV message and the proto field use the same value.
  2. Nil dereference if env.Rpc is nil — Added env.Rpc == nil guard in handleInboundRPC.
  3. Makefile clean redundancy — Removed $(RM) -r ./bin/darepod line.
  4. Validate() mailbox IDs — Added LocalMailboxID and RemoteMailboxID checks to Config.Validate().

From Codex review:
5. Tilde expansion bug — Fixed expandTilde to strip the leading / after ~ so filepath.Join produces the correct path.

Lint fixes:

  • Rewrapped cmd/darepod/main.go Long string to 80 cols
  • Added nolint:ll for unavoidable long const in indexer/client.go
  • Rewrapped comments and interface signatures in indexer/sync_client.go

All fixups squashed into their target commits, branch force-pushed.

@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from 558ec50 to 59b4f45 Compare February 23, 2026 20:02
@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review the latest push addressing your review comments: (1) MuSig2 pubkey serialization now uses schnorr.SerializePubKey for RC2+ and SerializeCompressed for v040, (2) added resp.Pubkey length guard in fetchOperatorTerms, (3) added TODO for future dispatcher methods, (4) improved compile-time interface check comment with NOTE about import cycle.

@claude

claude Bot commented Feb 23, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 2m 4s —— View job


Code Review

All four items from the previous round are addressed correctly. Detailed verification below.


Verified Fixes

1. MuSig2 pubkey serializationlndbackend/client_wallet.go:141-148

case input.MuSig2Version100RC2:
    signerBytes[i] = schnorr.SerializePubKey(pk)

default:
    signerBytes[i] = pk.SerializeCompressed()

Correct. RC2+ gets 32-byte x-only keys; v040 and any other version gets 33-byte compressed. The comment above the switch documents the format contract clearly.

2. resp.Pubkey length guarddarepod/server.go:677-679

if len(resp.Pubkey) == 0 {
    return nil, fmt.Errorf("operator pubkey is missing")
}

Clean early return before btcec.ParsePubKey. The diagnostic is clear.

3. TODO for future dispatcher methodsdarepod/server.go:414-419

The TODO comment explicitly names the future services and warns that missing entries cause silent drops in the ingress loop. This makes the failure mode visible to the next person adding a service.

4. Compile-time interface check commentlndbackend/client_wallet.go:47-54

The NOTE: explains both why the full check is omitted (import cycle) and where drift will surface (darepod/server.go where ClientWallet is passed to round.RoundClientConfig). Good.


One Minor Observation

default: in MuSig2CreateSession is implicitly forward-compatiblelndbackend/client_wallet.go:145

The default: case handles MuSig2Version040 and silently applies the same serialization to any future version constants lnd might introduce. The comment at line 135 lists only MuSig2Version040 as the default case, but the code catches everything. If a future lnd version introduces MuSig2Version200 with a different key format, the default: would silently use compressed encoding.

This is not an issue today since only MuSig2Version100RC2 is used throughout the codebase (lib/tree/node.go:838 is the only call site), but tightening the switch to case input.MuSig2Version040: with an explicit default: return nil, fmt.Errorf("unsupported musig2 version: %v", version) would surface any future version mismatches at runtime rather than silently using the wrong key format.

Not a blocker; noting it for awareness.


Summary

All action items from the previous three review rounds are resolved. The expandTilde Codex fix (config.go:197-218) is also correctly implemented — stripping the / separator before calling filepath.Join so "~/.darepod" produces the correct "/home/user/.darepod". The roundCfg.SelfRef post-assignment pattern (server.go:649-652) is safe: the pointer is stored by NewRoundClientActor, SelfRef is set before Start is called, and the comment documents the circular dependency. Code is ready to merge.

@sputn1ck sputn1ck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM! Started with some glueing between server and this and it seems to work, but would be very helpful to have a good master/base branch to start from

@sputn1ck
sputn1ck force-pushed the darepod-daemon-scaffold branch 2 times, most recently from f7a0c53 to 59b4f45 Compare February 25, 2026 16:00
@Roasbeef
Roasbeef force-pushed the indexer-draft-oor-lite branch 3 times, most recently from 3468f97 to c356dae Compare February 26, 2026 22:45
@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from 59b4f45 to 44b9984 Compare February 26, 2026 22:52
@Roasbeef
Roasbeef force-pushed the indexer-draft-oor-lite branch from c356dae to 7fdcca2 Compare February 26, 2026 22:59
Introduce the darepod package and cmd/darepod entry point for the
Ark client daemon. The daemon uses cobra for CLI flag parsing with
viper for config file and environment variable overrides.

Key components:

 - Config struct with nested LndConfig, ServerConfig, and RPCConfig
   sections, each with sensible defaults and validation.

 - Main entry point that wires signal interception (lnd signal
   package), config validation, and the server lifecycle.

 - Server struct with RunUntilShutdown that connects to lnd via
   lndclient (blocking until chain-synced and unlocked), starts
   a gRPC server for the daemon's own API, and blocks until
   SIGINT/SIGTERM.

 - Placeholder RPCServer with GetInfo returning version, commit,
   network, and lnd identity pubkey. The proto-generated stubs
   will replace the hand-written types in a follow-up commit.

 - Makefile updated to build darepod binary into ./bin/.
Introduce the daemonrpc package with a DaemonService proto
defining the client daemon's own gRPC API. The initial service
exposes a single GetInfo RPC that returns version, commit hash,
network, lnd identity pubkey, lnd alias, block height, and
server connection status.

The gen_protos.sh script is updated to generate stubs for the
new daemonrpc package while excluding it from mailboxrpc
generation since this is a local gRPC service, not a
mailbox-transported one.

The RPCServer and Server types are updated to use the generated
proto types instead of the hand-written placeholders.
Wire the ark server gRPC connection into the daemon startup flow.
The dialServer method supports three TLS modes: custom certificate,
system certificate pool, and insecure (for regtest/development).

The newMailboxEdge helper creates a MailboxServiceClient from the
established gRPC connection, ready to be passed into
serverconn.ConnectorConfig once the DeliveryStore is available.

GetInfo now calls lnd's ChainKit.GetBestBlock to populate the
block_height field instead of leaving it as a TODO.

The gen_protos.sh exclusion for daemonrpc is removed so mailboxrpc
stubs are generated for the DaemonService as well.
Initialize the actor system during daemon startup and register the
chain source actor backed by lndclient. The chain backend adapter
bridges lndclient's chain notifier, fee estimator, and wallet kit
into the unified ChainBackend interface consumed by the chainsource
actor, which provides fee estimation, block queries, and notification
subscription via dedicated sub-actors.

The actor system is gracefully shut down with a bounded timeout on
daemon exit. The chain backend lifecycle is also managed via deferred
Start/Stop calls.

ServerConfig gains LocalMailboxID and RemoteMailboxID fields (with
corresponding CLI flags) that will feed into the serverconn runtime
once the DeliveryStore is wired.
Register the RPCServer as a DaemonServiceMailboxServer on a ServeMux
so the ark server can invoke client-side RPCs (like GetInfo) through
the mailbox transport. The same RPCServer implementation serves both
the local gRPC interface and the mailbox RPC interface.

The ServeMux is stored on the Server struct and will be plugged into
the ConnectorConfig.Dispatchers map once the serverconn runtime is
wired with a persistent DeliveryStore.
In this commit, we replace the manual pointer-based flag bindings
(f.StringVar(&cfg.Field, ...)) with viper's BindPFlags + Unmarshal
pattern. Flags are declared with f.String/f.Bool (no pointer target),
bound to viper in bulk via BindPFlags, and then merged into the
Config struct in a PreRunE hook via v.Unmarshal(cfg).

This lets viper handle the full precedence chain (flags > env >
config file > defaults) in one place, and avoids the repetitive
boilerplate of threading each field through a pointer.
In this commit, we add MinVersion: tls.VersionTLS12 to both TLS
config paths in dialServer (custom cert pool and system cert pool).
Go's defaults already negotiate TLS 1.2+, but setting this
explicitly makes the floor visible in the code and prevents
accidental downgrade if defaults ever change.
In this commit, we fix several items flagged by the automated review:

- Add v.SetEnvKeyReplacer so nested viper keys like lnd.host map to
  DAREPOD_LND_HOST instead of the invalid DAREPOD_LND.HOST.

- Guard the expiresAt→uint64 cast in RegisterReceiveScriptTaproot
  against zero time.Time values that would wrap to a huge uint64.

- Add missing function comments on randomNonceHex and newTaprootScope.

- Add GoDoc field comments on TaprootScriptScope exported fields.

- Add TODO comments for the unused gRPC TLS config paths and the
  forward-declared newMailboxEdge helper.
In this commit, we add a ClientWallet adapter that bridges
lndclient's remote signing interfaces to the round.ClientWallet
interface (input.Signer + DeriveNextKey). This allows the round
actor's FSM to sign VTXO tree branches and forfeit transactions
through lnd's remote signer without requiring a local wallet.

The adapter wraps all MuSig2 session management methods
(CreateSession, RegisterNonces, Sign, CombineSig, Cleanup) and
handles the type conversions between lndclient's wire types and
the musig2 package types. SignOutputRaw and ComputeInputScript
forward to lnd's remote signer with proper prevout extraction
for taproot sighash computation.

Because input.Signer does not carry context, the adapter uses a
background context for all RPC calls. The underlying gRPC deadline
from the lndclient dial options still applies.
In this commit, we extend the ArkService.GetInfo response with the
full set of operator terms needed for round participation. The
client daemon needs these parameters before the round actor can
start, as they govern boarding script construction, VTXO exit
paths, forfeit penalties, and sweep timelocks.

New fields: boarding_exit_delay, vtxo_exit_delay, forfeit_script,
sweep_key, sweep_delay, dust_limit, min/max_boarding_amount,
fee_rate, and min_confirmations.
In this commit, we complete the daemon's startup sequence by
wiring the boarding wallet and round client actors into the
server lifecycle. The RunUntilShutdown method now follows an
11-step startup: lnd connect, server dial, actor system, chain
source, database, gRPC server, mailbox runtime, RPC clients,
wallet actor, round actor, and finally blocking until shutdown.

We extract the heavier initialization sequences into dedicated
helper methods (initDatabase, initRPCClients, initWalletActor,
initRoundActor) to keep RunUntilShutdown readable. The wallet
actor uses lndbackend.NewBoardingBackend for key derivation and
the db.Store convenience wrappers for persistence. The round
actor uses the new lndbackend.ClientWallet for MuSig2 signing
and fetches operator terms from the server via ArkService.GetInfo
before starting.
In this commit, we rewrap the comments, interface method
signatures, and const docstrings in sync_client.go to stay
within the 80-column line limit enforced by the linter.
@Roasbeef
Roasbeef force-pushed the darepod-daemon-scaffold branch from 44b9984 to f77f9b6 Compare February 26, 2026 23:14
@Roasbeef
Roasbeef changed the base branch from indexer-draft-oor-lite to main February 26, 2026 23:19
@Roasbeef
Roasbeef merged commit fd73515 into main Feb 26, 2026
16 checks passed
@Roasbeef
Roasbeef deleted the darepod-daemon-scaffold branch February 26, 2026 23:26
sputn1ck pushed a commit that referenced this pull request Mar 10, 2026
* darepod: add daemon skeleton with config, CLI, and server

Introduce the darepod package and cmd/darepod entry point for the
Ark client daemon. The daemon uses cobra for CLI flag parsing with
viper for config file and environment variable overrides.

Key components:

 - Config struct with nested LndConfig, ServerConfig, and RPCConfig
   sections, each with sensible defaults and validation.

 - Main entry point that wires signal interception (lnd signal
   package), config validation, and the server lifecycle.

 - Server struct with RunUntilShutdown that connects to lnd via
   lndclient (blocking until chain-synced and unlocked), starts
   a gRPC server for the daemon's own API, and blocks until
   SIGINT/SIGTERM.

 - Placeholder RPCServer with GetInfo returning version, commit,
   network, and lnd identity pubkey. The proto-generated stubs
   will replace the hand-written types in a follow-up commit.

 - Makefile updated to build darepod binary into ./bin/.

* daemonrpc: add daemon proto and GetInfo implementation

Introduce the daemonrpc package with a DaemonService proto
defining the client daemon's own gRPC API. The initial service
exposes a single GetInfo RPC that returns version, commit hash,
network, lnd identity pubkey, lnd alias, block height, and
server connection status.

The gen_protos.sh script is updated to generate stubs for the
new daemonrpc package while excluding it from mailboxrpc
generation since this is a local gRPC service, not a
mailbox-transported one.

The RPCServer and Server types are updated to use the generated
proto types instead of the hand-written placeholders.

* darepod: add server connection and lndclient wiring

Wire the ark server gRPC connection into the daemon startup flow.
The dialServer method supports three TLS modes: custom certificate,
system certificate pool, and insecure (for regtest/development).

The newMailboxEdge helper creates a MailboxServiceClient from the
established gRPC connection, ready to be passed into
serverconn.ConnectorConfig once the DeliveryStore is available.

GetInfo now calls lnd's ChainKit.GetBestBlock to populate the
block_height field instead of leaving it as a TODO.

The gen_protos.sh exclusion for daemonrpc is removed so mailboxrpc
stubs are generated for the DaemonService as well.

* darepod: wire actor system and chain source into startup

Initialize the actor system during daemon startup and register the
chain source actor backed by lndclient. The chain backend adapter
bridges lndclient's chain notifier, fee estimator, and wallet kit
into the unified ChainBackend interface consumed by the chainsource
actor, which provides fee estimation, block queries, and notification
subscription via dedicated sub-actors.

The actor system is gracefully shut down with a bounded timeout on
daemon exit. The chain backend lifecycle is also managed via deferred
Start/Stop calls.

ServerConfig gains LocalMailboxID and RemoteMailboxID fields (with
corresponding CLI flags) that will feed into the serverconn runtime
once the DeliveryStore is wired.

* darepod: register DaemonService on mailbox RPC mux

Register the RPCServer as a DaemonServiceMailboxServer on a ServeMux
so the ark server can invoke client-side RPCs (like GetInfo) through
the mailbox transport. The same RPCServer implementation serves both
the local gRPC interface and the mailbox RPC interface.

The ServeMux is stored on the Server struct and will be plugged into
the ConnectorConfig.Dispatchers map once the serverconn runtime is
wired with a persistent DeliveryStore.

* darepod: include cert path in TLS parse error

* cmd/darepod: use viper struct binding instead of manual flag vars

In this commit, we replace the manual pointer-based flag bindings
(f.StringVar(&cfg.Field, ...)) with viper's BindPFlags + Unmarshal
pattern. Flags are declared with f.String/f.Bool (no pointer target),
bound to viper in bulk via BindPFlags, and then merged into the
Config struct in a PreRunE hook via v.Unmarshal(cfg).

This lets viper handle the full precedence chain (flags > env >
config file > defaults) in one place, and avoids the repetitive
boilerplate of threading each field through a pointer.

* darepod: set TLS minimum version to 1.2 for server dial

In this commit, we add MinVersion: tls.VersionTLS12 to both TLS
config paths in dialServer (custom cert pool and system cert pool).
Go's defaults already negotiate TLS 1.2+, but setting this
explicitly makes the floor visible in the code and prevents
accidental downgrade if defaults ever change.

* multi: address review feedback from Claude bot

In this commit, we fix several items flagged by the automated review:

- Add v.SetEnvKeyReplacer so nested viper keys like lnd.host map to
  DAREPOD_LND_HOST instead of the invalid DAREPOD_LND.HOST.

- Guard the expiresAt→uint64 cast in RegisterReceiveScriptTaproot
  against zero time.Time values that would wrap to a huge uint64.

- Add missing function comments on randomNonceHex and newTaprootScope.

- Add GoDoc field comments on TaprootScriptScope exported fields.

- Add TODO comments for the unused gRPC TLS config paths and the
  forward-declared newMailboxEdge helper.

* lndbackend: add ClientWallet adapter for round actor signing

In this commit, we add a ClientWallet adapter that bridges
lndclient's remote signing interfaces to the round.ClientWallet
interface (input.Signer + DeriveNextKey). This allows the round
actor's FSM to sign VTXO tree branches and forfeit transactions
through lnd's remote signer without requiring a local wallet.

The adapter wraps all MuSig2 session management methods
(CreateSession, RegisterNonces, Sign, CombineSig, Cleanup) and
handles the type conversions between lndclient's wire types and
the musig2 package types. SignOutputRaw and ComputeInputScript
forward to lnd's remote signer with proper prevout extraction
for taproot sighash computation.

Because input.Signer does not carry context, the adapter uses a
background context for all RPC calls. The underlying gRPC deadline
from the lndclient dial options still applies.

* arkrpc: add operator terms fields to GetInfo proto

In this commit, we extend the ArkService.GetInfo response with the
full set of operator terms needed for round participation. The
client daemon needs these parameters before the round actor can
start, as they govern boarding script construction, VTXO exit
paths, forfeit penalties, and sweep timelocks.

New fields: boarding_exit_delay, vtxo_exit_delay, forfeit_script,
sweep_key, sweep_delay, dust_limit, min/max_boarding_amount,
fee_rate, and min_confirmations.

* darepod: wire wallet and round actors into daemon startup

In this commit, we complete the daemon's startup sequence by
wiring the boarding wallet and round client actors into the
server lifecycle. The RunUntilShutdown method now follows an
11-step startup: lnd connect, server dial, actor system, chain
source, database, gRPC server, mailbox runtime, RPC clients,
wallet actor, round actor, and finally blocking until shutdown.

We extract the heavier initialization sequences into dedicated
helper methods (initDatabase, initRPCClients, initWalletActor,
initRoundActor) to keep RunUntilShutdown readable. The wallet
actor uses lndbackend.NewBoardingBackend for key derivation and
the db.Store convenience wrappers for persistence. The round
actor uses the new lndbackend.ClientWallet for MuSig2 signing
and fetches operator terms from the server via ArkService.GetInfo
before starting.

* indexer: wrap sync_client comments and signatures to 80 cols

In this commit, we rewrap the comments, interface method
signatures, and const docstrings in sync_client.go to stay
within the 80-column line limit enforced by the linter.
sputn1ck pushed a commit that referenced this pull request Mar 13, 2026
* darepod: add daemon skeleton with config, CLI, and server

Introduce the darepod package and cmd/darepod entry point for the
Ark client daemon. The daemon uses cobra for CLI flag parsing with
viper for config file and environment variable overrides.

Key components:

 - Config struct with nested LndConfig, ServerConfig, and RPCConfig
   sections, each with sensible defaults and validation.

 - Main entry point that wires signal interception (lnd signal
   package), config validation, and the server lifecycle.

 - Server struct with RunUntilShutdown that connects to lnd via
   lndclient (blocking until chain-synced and unlocked), starts
   a gRPC server for the daemon's own API, and blocks until
   SIGINT/SIGTERM.

 - Placeholder RPCServer with GetInfo returning version, commit,
   network, and lnd identity pubkey. The proto-generated stubs
   will replace the hand-written types in a follow-up commit.

 - Makefile updated to build darepod binary into ./bin/.

* daemonrpc: add daemon proto and GetInfo implementation

Introduce the daemonrpc package with a DaemonService proto
defining the client daemon's own gRPC API. The initial service
exposes a single GetInfo RPC that returns version, commit hash,
network, lnd identity pubkey, lnd alias, block height, and
server connection status.

The gen_protos.sh script is updated to generate stubs for the
new daemonrpc package while excluding it from mailboxrpc
generation since this is a local gRPC service, not a
mailbox-transported one.

The RPCServer and Server types are updated to use the generated
proto types instead of the hand-written placeholders.

* darepod: add server connection and lndclient wiring

Wire the ark server gRPC connection into the daemon startup flow.
The dialServer method supports three TLS modes: custom certificate,
system certificate pool, and insecure (for regtest/development).

The newMailboxEdge helper creates a MailboxServiceClient from the
established gRPC connection, ready to be passed into
serverconn.ConnectorConfig once the DeliveryStore is available.

GetInfo now calls lnd's ChainKit.GetBestBlock to populate the
block_height field instead of leaving it as a TODO.

The gen_protos.sh exclusion for daemonrpc is removed so mailboxrpc
stubs are generated for the DaemonService as well.

* darepod: wire actor system and chain source into startup

Initialize the actor system during daemon startup and register the
chain source actor backed by lndclient. The chain backend adapter
bridges lndclient's chain notifier, fee estimator, and wallet kit
into the unified ChainBackend interface consumed by the chainsource
actor, which provides fee estimation, block queries, and notification
subscription via dedicated sub-actors.

The actor system is gracefully shut down with a bounded timeout on
daemon exit. The chain backend lifecycle is also managed via deferred
Start/Stop calls.

ServerConfig gains LocalMailboxID and RemoteMailboxID fields (with
corresponding CLI flags) that will feed into the serverconn runtime
once the DeliveryStore is wired.

* darepod: register DaemonService on mailbox RPC mux

Register the RPCServer as a DaemonServiceMailboxServer on a ServeMux
so the ark server can invoke client-side RPCs (like GetInfo) through
the mailbox transport. The same RPCServer implementation serves both
the local gRPC interface and the mailbox RPC interface.

The ServeMux is stored on the Server struct and will be plugged into
the ConnectorConfig.Dispatchers map once the serverconn runtime is
wired with a persistent DeliveryStore.

* darepod: include cert path in TLS parse error

* cmd/darepod: use viper struct binding instead of manual flag vars

In this commit, we replace the manual pointer-based flag bindings
(f.StringVar(&cfg.Field, ...)) with viper's BindPFlags + Unmarshal
pattern. Flags are declared with f.String/f.Bool (no pointer target),
bound to viper in bulk via BindPFlags, and then merged into the
Config struct in a PreRunE hook via v.Unmarshal(cfg).

This lets viper handle the full precedence chain (flags > env >
config file > defaults) in one place, and avoids the repetitive
boilerplate of threading each field through a pointer.

* darepod: set TLS minimum version to 1.2 for server dial

In this commit, we add MinVersion: tls.VersionTLS12 to both TLS
config paths in dialServer (custom cert pool and system cert pool).
Go's defaults already negotiate TLS 1.2+, but setting this
explicitly makes the floor visible in the code and prevents
accidental downgrade if defaults ever change.

* multi: address review feedback from Claude bot

In this commit, we fix several items flagged by the automated review:

- Add v.SetEnvKeyReplacer so nested viper keys like lnd.host map to
  DAREPOD_LND_HOST instead of the invalid DAREPOD_LND.HOST.

- Guard the expiresAt→uint64 cast in RegisterReceiveScriptTaproot
  against zero time.Time values that would wrap to a huge uint64.

- Add missing function comments on randomNonceHex and newTaprootScope.

- Add GoDoc field comments on TaprootScriptScope exported fields.

- Add TODO comments for the unused gRPC TLS config paths and the
  forward-declared newMailboxEdge helper.

* lndbackend: add ClientWallet adapter for round actor signing

In this commit, we add a ClientWallet adapter that bridges
lndclient's remote signing interfaces to the round.ClientWallet
interface (input.Signer + DeriveNextKey). This allows the round
actor's FSM to sign VTXO tree branches and forfeit transactions
through lnd's remote signer without requiring a local wallet.

The adapter wraps all MuSig2 session management methods
(CreateSession, RegisterNonces, Sign, CombineSig, Cleanup) and
handles the type conversions between lndclient's wire types and
the musig2 package types. SignOutputRaw and ComputeInputScript
forward to lnd's remote signer with proper prevout extraction
for taproot sighash computation.

Because input.Signer does not carry context, the adapter uses a
background context for all RPC calls. The underlying gRPC deadline
from the lndclient dial options still applies.

* arkrpc: add operator terms fields to GetInfo proto

In this commit, we extend the ArkService.GetInfo response with the
full set of operator terms needed for round participation. The
client daemon needs these parameters before the round actor can
start, as they govern boarding script construction, VTXO exit
paths, forfeit penalties, and sweep timelocks.

New fields: boarding_exit_delay, vtxo_exit_delay, forfeit_script,
sweep_key, sweep_delay, dust_limit, min/max_boarding_amount,
fee_rate, and min_confirmations.

* darepod: wire wallet and round actors into daemon startup

In this commit, we complete the daemon's startup sequence by
wiring the boarding wallet and round client actors into the
server lifecycle. The RunUntilShutdown method now follows an
11-step startup: lnd connect, server dial, actor system, chain
source, database, gRPC server, mailbox runtime, RPC clients,
wallet actor, round actor, and finally blocking until shutdown.

We extract the heavier initialization sequences into dedicated
helper methods (initDatabase, initRPCClients, initWalletActor,
initRoundActor) to keep RunUntilShutdown readable. The wallet
actor uses lndbackend.NewBoardingBackend for key derivation and
the db.Store convenience wrappers for persistence. The round
actor uses the new lndbackend.ClientWallet for MuSig2 signing
and fetches operator terms from the server via ArkService.GetInfo
before starting.

* indexer: wrap sync_client comments and signatures to 80 cols

In this commit, we rewrap the comments, interface method
signatures, and const docstrings in sync_client.go to stay
within the 80-column line limit enforced by the linter.
ellemouton added a commit that referenced this pull request Mar 17, 2026
systest: adapt for client IntentPackage refactor
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