Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions arkrpc/indexer.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions arkrpc/indexer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ message VTXO {
// oor_final_checkpoint_psbts are serialized finalized checkpoint PSBTs for
// virtual/OOR VTXOs.
repeated bytes oor_final_checkpoint_psbts = 14;

// chain_depth is the number of OOR checkpoint hops between this VTXO
// and the most recent on-chain commitment. Round-created VTXOs have
// chain_depth 0.
uint32 chain_depth = 15;
}

message ListVTXOsByScriptsRequest {
Expand Down
22 changes: 18 additions & 4 deletions daemonrpc/daemon.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions daemonrpc/daemon.proto
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ message VTXO {
// commitment_txid is the hex-encoded txid of the on-chain commitment
// transaction anchoring this VTXO's tree.
string commitment_txid = 9;

// chain_depth is the number of OOR checkpoint hops between this VTXO
// and the most recent on-chain commitment. Round-created VTXOs have
// chain_depth 0.
uint32 chain_depth = 10;
}

message ListVTXOsRequest {
Expand Down
2 changes: 2 additions & 0 deletions darepod/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ gRPC API.
- Board RPC is non-blocking: delegates to wallet actor and returns immediately.
- ListRounds splits pending (in-memory from actor) and persisted (SQL with cursor pagination) rounds.
- Server holds a `roundStore` reference for direct SQL queries from the RPC layer.
- Actor startup order: VTXO manager starts before round actor and OOR actor, so the manager ref is available for both. The round actor ref in the VTXO manager is lazy (service-key-based, resolved at Tell time).
- `mapRoundVTXOManagerMsg` bridges `round.VTXOManagerMsg` → `vtxo.ManagerMsg` via `MapInputRef`. Compile-time assertions enforce that all `round.VTXOManagerMsg` implementors satisfy `vtxo.ManagerMsg`.

## Deep Docs

Expand Down
2 changes: 2 additions & 0 deletions darepod/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ gRPC API.
- Board RPC is non-blocking: delegates to wallet actor and returns immediately.
- ListRounds splits pending (in-memory from actor) and persisted (SQL with cursor pagination) rounds.
- Server holds a `roundStore` reference for direct SQL queries from the RPC layer.
- Actor startup order: VTXO manager starts before round actor and OOR actor, so the manager ref is available for both. The round actor ref in the VTXO manager is lazy (service-key-based, resolved at Tell time).
- `mapRoundVTXOManagerMsg` bridges `round.VTXOManagerMsg` → `vtxo.ManagerMsg` via `MapInputRef`. Compile-time assertions enforce that all `round.VTXOManagerMsg` implementors satisfy `vtxo.ManagerMsg`.

## Deep Docs

Expand Down
1 change: 1 addition & 0 deletions darepod/rpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ func descriptorToProto(v *vtxo.Descriptor) *daemonrpc.VTXO {
RelativeExpiry: v.RelativeExpiry,
PkScript: hex.EncodeToString(v.PkScript),
CommitmentTxid: v.CommitmentTxID.String(),
ChainDepth: uint32(v.ChainDepth),
}
}

Expand Down
127 changes: 116 additions & 11 deletions darepod/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/lightninglabs/darepo-client/rpc/roundpb"
"github.com/lightninglabs/darepo-client/serverconn"
"github.com/lightninglabs/darepo-client/timeout"
"github.com/lightninglabs/darepo-client/vtxo"
"github.com/lightninglabs/darepo-client/wallet"
"github.com/lightninglabs/lndclient"
lndbuild "github.com/lightningnetwork/lnd/build"
Expand Down Expand Up @@ -766,18 +767,34 @@ func (s *Server) startWalletDependentActors(ctx context.Context,
s.walletRef = fn.Some(walletRef)

// -------------------------------------------------------
// 10. Register the round client actor.
// 10. Start the VTXO manager before the round actor so
// the manager ref can be passed directly in the round
// config, avoiding a post-Start mutation.
// -------------------------------------------------------
if err := s.initRoundActor(
vtxoManagerRef, err := s.initVTXOManager(ctx, chainSourceRef)
if err != nil {
return err
}

roundVTXOManager := actor.NewMapInputRef(
vtxoManagerRef, mapRoundVTXOManagerMsg,
)

// -------------------------------------------------------
// 11. Register the round client actor.
// -------------------------------------------------------
_, err = s.initRoundActor(
ctx, chainSourceRef, walletRef, timeoutRef,
); err != nil {
roundVTXOManager,
)
if err != nil {
return err
}

// -------------------------------------------------------
// 11. Register the OOR client actor.
// 12. Register the OOR client actor.
// -------------------------------------------------------
if err := s.initOORActor(ctx); err != nil {
if err := s.initOORActor(ctx, vtxoManagerRef); err != nil {
return err
}

Expand Down Expand Up @@ -1514,7 +1531,9 @@ func (s *Server) initRoundActor(ctx context.Context,
walletRef actor.ActorRef[
wallet.WalletMsg, wallet.WalletResp,
],
timeoutRef actor.TellOnlyRef[timeout.Msg]) error {
timeoutRef actor.TellOnlyRef[timeout.Msg],
vtxoManager actor.TellOnlyRef[round.VTXOManagerMsg],
) (*round.RoundClientActor, error) {

// Select the client wallet (signing) backend based on
// wallet type. In lnd mode, signing goes through lnd's
Expand Down Expand Up @@ -1545,7 +1564,7 @@ func (s *Server) initRoundActor(ctx context.Context,
// and other round parameters.
operatorTerms, err := s.fetchOperatorTerms(ctx)
if err != nil {
return fmt.Errorf("unable to fetch operator "+
return nil, fmt.Errorf("unable to fetch operator "+
"terms: %w", err)
}

Expand All @@ -1569,6 +1588,7 @@ func (s *Server) initRoundActor(ctx context.Context,
ActorSystem: s.actorSystem,
TimeoutActor: timeoutRef,
MaxOperatorFee: defaultMaxOperatorFee,
VTXOManager: vtxoManager,
ForfeitCollectionTimeout: s.cfg.
ForfeitCollectionTimeout,
}
Expand All @@ -1577,7 +1597,7 @@ func (s *Server) initRoundActor(ctx context.Context,
roundCfg,
).Unpack()
if err != nil {
return fmt.Errorf("unable to create round "+
return nil, fmt.Errorf("unable to create round "+
"actor: %w", err)
}

Expand All @@ -1593,13 +1613,71 @@ func (s *Server) initRoundActor(ctx context.Context,
roundCfg.SelfRef = roundRef

if err := roundActor.Start(ctx); err != nil {
return fmt.Errorf("unable to start round "+
return nil, fmt.Errorf("unable to start round "+
"actor: %w", err)
}

log.InfoS(ctx, "Round actor registered and started")

return nil
return roundActor, nil
}

// initVTXOManager creates, registers, and starts the VTXO manager actor.
// The manager recovers persisted VTXOs on startup and spawns one VTXO actor
// per live descriptor.
func (s *Server) initVTXOManager(ctx context.Context,
chainSourceRef actor.ActorRef[
chainsource.ChainSourceMsg, chainsource.ChainSourceResp,
],
) (actor.ActorRef[vtxo.ManagerMsg, vtxo.ManagerResp], error) {

var vtxoWallet vtxo.VTXOWallet
switch s.cfg.Wallet.Type {
case WalletTypeLnd:
lndSvc := s.lnd.UnsafeFromSome()
vtxoWallet = lndbackend.NewClientWallet(
lndSvc.Signer, lndSvc.WalletKit,
)

case WalletTypeLwwallet:
vtxoWallet = s.lwWallet.UnsafeFromSome()
}

clk := clock.NewDefaultClock()
dbStore := db.NewStore(
s.db.DB, s.db.Queries, s.db.Backend(), log,
)
vtxoStore := dbStore.NewVTXOStore(clk)

manager := vtxo.NewManager(&vtxo.ManagerConfig{
Store: vtxoStore,
Wallet: vtxoWallet,
ChainSource: chainSourceRef,
ActorSystem: s.actorSystem,
ChainParams: s.chainParams,
Log: fn.Some(log),
RoundActor: round.NewServiceKey().Ref(s.actorSystem),
})

managerKey := actor.NewServiceKey[vtxo.ManagerMsg, vtxo.ManagerResp](
"vtxo-manager",
)
managerRef := actor.RegisterWithSystem(
s.actorSystem, "vtxo-manager", managerKey, manager,
)

err := manager.Start(ctx, managerRef)
if err != nil {
s.actorSystem.StopAndRemoveActor("vtxo-manager")

var zero actor.ActorRef[vtxo.ManagerMsg, vtxo.ManagerResp]

return zero, fmt.Errorf("unable to start vtxo manager: %w", err)
}

log.InfoS(ctx, "VTXO manager registered and started")

return managerRef, nil
}

// initOORActor creates and starts the OOR (out-of-round) client actor.
Expand All @@ -1615,7 +1693,9 @@ func (s *Server) initRoundActor(ctx context.Context,
// incoming VTXOs, handles incoming ack.
// - SigningOutboxHandler (Next delegate): signs Ark and checkpoint
// PSBTs, schedules retries.
func (s *Server) initOORActor(ctx context.Context) error {
func (s *Server) initOORActor(ctx context.Context,
vtxoManagerRef actor.TellOnlyRef[vtxo.ManagerMsg]) error {

clk := clock.NewDefaultClock()
dbStore := db.NewStore(
s.db.DB, s.db.Queries, s.db.Backend(), log,
Expand Down Expand Up @@ -1663,6 +1743,7 @@ func (s *Server) initOORActor(ctx context.Context) error {
DeliveryStore: s.deliveryStore,
ActorSystem: s.actorSystem,
ActorID: oor.OORActorServiceKeyName,
VTXOManager: vtxoManagerRef,
})

// Wire the timeout callback ref using the registered service
Expand All @@ -1682,6 +1763,30 @@ func (s *Server) initOORActor(ctx context.Context) error {
return nil
}

// Compile-time assertions: every round.VTXOManagerMsg implementor must
// also satisfy vtxo.ManagerMsg. This makes the runtime assertion in
// mapRoundVTXOManagerMsg infallible.
var _ vtxo.ManagerMsg = (*round.VTXOCreatedNotification)(nil)
var _ vtxo.ManagerMsg = (*round.VTXOTerminatedMsg)(nil)

// mapRoundVTXOManagerMsg adapts round-owned manager notifications into the
// concrete message type accepted by the VTXO manager actor.
func mapRoundVTXOManagerMsg(msg round.VTXOManagerMsg) vtxo.ManagerMsg {
// The compile-time assertions above guarantee this succeeds for
// all concrete types that implement round.VTXOManagerMsg.
mapped, ok := msg.(vtxo.ManagerMsg)
if !ok {
log.ErrorS(context.TODO(),
"Unexpected VTXO manager msg type, dropping",
nil, slog.String("type",
fmt.Sprintf("%T", msg)))

return nil
}

return mapped
}

// fetchOperatorTerms retrieves the operator's terms from the Ark
// server via the ArkService.GetInfo RPC. The terms include the
// operator pubkey, sweep delay, VTXO exit delay, forfeit script, dust
Expand Down
3 changes: 2 additions & 1 deletion db/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Supports SQLite and PostgreSQL backends.
- `RoundStore` — Interface for round state persistence (CommitState, FetchState, ListRoundsPaginated).
- `RoundPersistenceStore` — Concrete implementation wrapping `BatchedTx[RoundStore]` with domain conversion.
- `RoundSummary` / `VTXOSummary` — Lightweight descriptors for paginated round listing (avoids deserializing full trees).
- `VTXOPersistenceStore` — Persistent store for VTXO descriptors (InsertClientVTXO, FetchByOutpoint).
- `VTXOPersistenceStore` — Persistent store for VTXO descriptors (InsertClientVTXO, FetchByOutpoint). Persists `ChainDepth` (OOR hop count) alongside other VTXO metadata.
- `OORArtifactStore` — Interface for OOR session state persistence.

## Relationships
Expand All @@ -28,6 +28,7 @@ Supports SQLite and PostgreSQL backends.
- Round checkpoints include commitment tx, VTXO tree, client sub-trees, boarding signatures, and every intent with updated status.
- Default retry logic: 10 retries with exponential backoff (40ms initial, capped at 3s).
- **Never write raw SQL in Go** — add queries to `db/queries/`, regenerate with `make sqlc`.
- Latest migration: `000005_vtxo_chain_depth` adds `chain_depth INTEGER NOT NULL DEFAULT 0` to `vtxos` table. UPSERT uses zero-value sentinel pattern (same as `tree_depth`, `batch_expiry`): zero means "not yet populated", non-zero overwrites.

## Deep Docs

Expand Down
3 changes: 2 additions & 1 deletion db/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Supports SQLite and PostgreSQL backends.
- `RoundStore` — Interface for round state persistence (CommitState, FetchState, ListRoundsPaginated).
- `RoundPersistenceStore` — Concrete implementation wrapping `BatchedTx[RoundStore]` with domain conversion.
- `RoundSummary` / `VTXOSummary` — Lightweight descriptors for paginated round listing (avoids deserializing full trees).
- `VTXOPersistenceStore` — Persistent store for VTXO descriptors (InsertClientVTXO, FetchByOutpoint).
- `VTXOPersistenceStore` — Persistent store for VTXO descriptors (InsertClientVTXO, FetchByOutpoint). Persists `ChainDepth` (OOR hop count) alongside other VTXO metadata.
- `OORArtifactStore` — Interface for OOR session state persistence.

## Relationships
Expand All @@ -28,6 +28,7 @@ Supports SQLite and PostgreSQL backends.
- Round checkpoints include commitment tx, VTXO tree, client sub-trees, boarding signatures, and every intent with updated status.
- Default retry logic: 10 retries with exponential backoff (40ms initial, capped at 3s).
- **Never write raw SQL in Go** — add queries to `db/queries/`, regenerate with `make sqlc`.
- Latest migration: `000005_vtxo_chain_depth` adds `chain_depth INTEGER NOT NULL DEFAULT 0` to `vtxos` table. UPSERT uses zero-value sentinel pattern (same as `tree_depth`, `batch_expiry`): zero means "not yet populated", non-zero overwrites.

## Deep Docs

Expand Down
1 change: 1 addition & 0 deletions db/actordelivery/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading