Skip to content

multi: implement missing vtxo db persistence, add refresh route via wallet - #93

Merged
Roasbeef merged 11 commits into
mainfrom
roudn-forfeit-server-catch-up
Jan 31, 2026
Merged

multi: implement missing vtxo db persistence, add refresh route via wallet#93
Roasbeef merged 11 commits into
mainfrom
roudn-forfeit-server-catch-up

Conversation

@Roasbeef

Copy link
Copy Markdown
Member

This PR adds VTXO lifecycle persistence support to enable the refresh flow where clients can forfeit old VTXOs to receive new ones in a subsequent round.

The core addition is a new database migration (000004_vtxo_status) that extends the vtxos table with status tracking fields. VTXOs now track their lifecycle state (Live, RefreshRequested, Forfeiting, Forfeited, Spent, Expiring, Failed) along with forfeit transaction data for crash recovery. The schema also includes replacement tracking so forfeited VTXOs can be linked to their successors for auditing purposes.

A new VTXOPersistenceStore implements the vtxo.VTXOStore interface using the BatchedTx pattern for transaction-safe operations. The store handles saving new VTXOs with serialized tree paths, retrieving VTXOs for actor recovery on startup, atomic status transitions, and persisting signed forfeit transactions so they can be recovered after a crash.

The wallet package gains a new RefreshVTXOsRequest/Response message pair that forms the high-level API for triggering refresh. The wallet actor handler looks up target VTXOs from the store, optionally filters by expiry threshold, and forwards RefreshVTXORequest messages to the round actor for each eligible VTXO.

A small type consistency fix changes SubmitVTXOForfeitSigsToServer to use the RoundID type instead of a bare string, aligning it with other round messages for better type safety.

All sqlc queries have been regenerated to include the new VTXO lifecycle operations including InsertVTXO, GetVTXO, ListLiveVTXOs, UpdateVTXOStatus, MarkVTXOForfeiting, MarkVTXOForfeited, GetVTXOForfeitTx, and DeleteVTXO.

This is all prep for a new set of systests for refresh behavior.

@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 significantly enhances the VTXO management system by introducing robust persistence for VTXO lifecycle states and supporting the VTXO refresh flow. It lays the groundwork for clients to forfeit old VTXOs and receive new ones, improving the system's ability to handle VTXO expiration and recovery. The changes include a new database migration, a dedicated persistence store, and a new wallet API for initiating refresh operations, all while improving type safety and ensuring crash recovery for critical transactions.

Highlights

  • VTXO Lifecycle Persistence: Introduced a new database migration (000004_vtxo_status) to add status tracking fields (Live, RefreshRequested, Forfeiting, Forfeited, Spent, Expiring, Failed) and forfeit transaction data to the vtxos table for crash recovery and auditing.
  • New VTXOPersistenceStore: Implemented a new store that uses the BatchedTx pattern to handle transaction-safe VTXO operations, including saving, retrieving, status transitions, and persisting signed forfeit transactions.
  • Refresh Flow API: Added RefreshVTXOsRequest and RefreshVTXOsResponse messages to the wallet package, providing a high-level API for triggering VTXO refresh.
  • Type Safety Improvements: Enhanced type consistency by changing SubmitVTXOForfeitSigsToServer to use the RoundID type instead of a bare string and updated signature handling to use *schnorr.Signature objects directly.
  • SQLC Query Regeneration: All sqlc queries were regenerated to incorporate the new VTXO lifecycle operations, ensuring proper database interaction for the new features.

🧠 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.

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.

-- This approach preserves existing data while removing the new columns.

-- Create a temporary table with the original schema.
CREATE TABLE vtxos_backup (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No need to make this new migration, can just edit the existing schema in place.

@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 significant and well-structured changes to support VTXO lifecycle persistence, which is a crucial step for enabling the refresh flow. The database migration adds necessary status tracking fields to the vtxos table, and the new VTXOPersistenceStore provides a clean, transaction-safe interface for managing VTXO state. The introduction of RefreshVTXOsRequest/Response messages in the wallet package correctly sets up the high-level API for this new functionality.

I've identified a critical issue in the down migration script that needs to be addressed. Additionally, there are a couple of medium-severity suggestions regarding code duplication in test helpers and formatting in a generated schema file to improve maintainability.

Overall, this is a solid contribution that lays important groundwork. The move towards stronger types like *schnorr.Signature and round.RoundID is also a great improvement for type safety and code clarity.

Comment thread db/sqlc/schemas/generated_schema.sql Outdated
-- last_update_time is the unix epoch timestamp when this VTXO was last
-- modified, such as when it was marked as spent.
last_update_time BIGINT NOT NULL,
last_update_time BIGINT NOT NULL, status INTEGER NOT NULL DEFAULT 0, forfeit_round_id TEXT, forfeit_tx BLOB, forfeit_txid BLOB, replaced_by_hash BLOB, replaced_by_index INTEGER,

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

The formatting of the new columns in the vtxos table definition is difficult to read as they are all on a single line. While this is a generated file, it would be beneficial for schema readability if each new column definition was on its own line. This might be influenced by how the ALTER TABLE statements are structured in the up migration file or a setting in the schema generation tool.

Comment thread vtxo/actor_test.go Outdated
Comment on lines +17 to +28
// testSchnorrSignature creates a deterministic schnorr signature for tests.
func testSchnorrSignature(t *testing.T, seed string) *schnorr.Signature {
t.Helper()

h := chainhash.HashH([]byte(seed))
privKey, _ := btcec.PrivKeyFromBytes(h[:])
msg := chainhash.HashH([]byte("test message"))
sig, err := schnorr.Sign(privKey, msg[:])
require.NoError(t, err)

return sig
}

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 testSchnorrSignature helper function is a duplicate of the one defined in round/transitions_test.go. To improve maintainability and avoid code duplication, consider moving this function to a shared test utility package where it can be accessed by both vtxo and round package tests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 741f1a3828

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread db/sqlc/queries/vtxo.sql Outdated
Comment on lines +15 to +16
SELECT * FROM vtxos
WHERE status < 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude spent VTXOs from live recovery query

ListLiveVTXOs (used for recovery) filters only on status < 3. Because the migration defaults status to 0 and MarkVTXOSpent still only toggles the legacy spent flag, any VTXO that is already spent (including pre-migration rows) will continue to have status 0 and will be returned as live on restart, causing spent VTXOs to be resurrected and acted on. Consider backfilling/updating status on spend or also filtering on spent = FALSE here.

Useful? React with 👍 / 👎.

Comment thread wallet/wallet.go Outdated
Comment on lines +531 to +535
// TODO(roasbeef): Full implementation requires vtxoStore and roundActor
// dependencies to be added to the wallet actor. For now, return success
// to indicate the request was accepted. The systest will wire up the
// actual refresh flow via direct round actor calls.
resp := &RefreshVTXOsResponse{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don’t report refresh success when no refresh occurs

handleRefreshVTXOs currently only logs and returns a success response with RefreshingCount derived from the request, but it never enqueues a refresh or consults ForceRefresh/expiry. Any client using this new API will receive a success response even though no VTXO is actually refreshed, which can silently leave expiring VTXOs untouched. Until the wiring is in place, consider returning an explicit error or zero counts.

Useful? React with 👍 / 👎.

@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch from 295194b to 4519c28 Compare January 29, 2026 21:13
@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch from 4519c28 to d0cde8f Compare January 29, 2026 21:33
@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review this

@codex review this

@claude

claude Bot commented Jan 29, 2026

Copy link
Copy Markdown

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


Code Review Complete

I've reviewed this PR thoroughly. The implementation is well-structured and follows good design patterns. Below are my findings organized by severity.

Summary

This PR adds VTXO lifecycle persistence to enable the refresh flow. The core changes include extending the database schema with status tracking fields, implementing VTXOPersistenceStore for transaction-safe operations, and adding wallet-level refresh APIs. The work is solid, but there are several important issues to address.


Critical Issues

1. Migration Design Issue (Per Author's Comment)

Location: db/sqlc/migrations/000003_round_tables.up.sql

As @Roasbeef noted in the review, this shouldn't be a separate migration. The VTXO status fields should be integrated into the existing schema in place rather than adding them via ALTER TABLE. This creates unnecessary migration complexity.

Recommendation: Collapse the status-related columns into the initial CREATE TABLE statement in the original migration.


2. Spent VTXOs Resurrection Bug

Location: db/sqlc/queries/vtxo.sql:16

-- ListLiveVTXOs returns all VTXOs that are not in a terminal state.
SELECT * FROM vtxos
WHERE status < 3
ORDER BY creation_time DESC;

Issue: The query filters only on status < 3 (Live=0, RefreshRequested=1, Forfeiting=2), but VTXOs can have status=0 (default) while also having spent=TRUE from the legacy spent flag. This means spent VTXOs will be returned as "live" during recovery, potentially causing them to be resurrected and acted upon after restart.

Root cause: The migration sets status INTEGER NOT NULL DEFAULT 0 but doesn't backfill existing spent VTXOs to status=4 (Spent). The code at db/vtxo_store.go also doesn't coordinate the two fields properly.

Recommendation: Either:

  • Add AND spent = FALSE to the ListLiveVTXOs query
  • Backfill the status column when the migration runs: UPDATE vtxos SET status = 4 WHERE spent = TRUE
  • Remove the legacy spent column entirely and use only status

Fix this →


High Priority Issues

3. Wallet Refresh Handler Returns False Success

Location: wallet/wallet.go:533-556

func (a *Ark) handleRefreshVTXOs(ctx context.Context,
	req *RefreshVTXOsRequest) fn.Result[WalletResp] {

	// ... logs the request ...

	// Forward to round actor if configured.
	a.roundActor.WhenSome(func(ref actor.TellOnlyRef[actormsg.RoundReceivable]) {
		ref.Tell(ctx, &actormsg.TriggerVTXORefreshMsg{
			TargetOutpoints: req.TargetOutpoints,
			ForceRefresh:    req.ForceRefresh,
		})
	})

	resp := &RefreshVTXOsResponse{
		RefreshingCount: len(req.TargetOutpoints),  // Always returns count from request
		Errors:          make(map[wire.OutPoint]error),
	}

	return fn.Ok[WalletResp](resp)
}

Issue: The handler always returns success with RefreshingCount equal to the request count, even though:

  1. No actual work is performed to verify VTXOs exist or are eligible for refresh
  2. The ForceRefresh flag is ignored locally (only forwarded)
  3. If roundActor is None, nothing happens but success is still returned
  4. No errors are ever populated in the Errors map

This creates a false API contract where callers think VTXOs are being refreshed when they may not be.

Recommendation:

  • Return an error if roundActor is None
  • Consider making this synchronous to get actual feedback from the round actor
  • Or document clearly that this is "fire and forget" with no guarantees

Fix this →


4. Missing Function Comments

Location: Multiple files

The codebase style guide at docs/development_guidelines.md states:

At a minimum every function must be commented with its intended purpose and any assumptions that it makes

Several functions are missing comments:

  • db/vtxo_store.go:274 - descriptorToInsertParams
  • db/vtxo_store.go:321 - rowToDescriptor

Example of proper format:

// descriptorToInsertParams converts a vtxo.Descriptor to sqlc insert
// parameters. This handles serialization of the tree path and public keys
// for database storage.
func (s *VTXOPersistenceStore) descriptorToInsertParams(
	desc *vtxo.Descriptor,
) (InsertVTXOParams, error) {

These functions do have comments, but they could be more detailed per the guidelines' emphasis on explaining how/why rather than just what.


Medium Priority Issues

5. Inconsistent Signature Type Usage

Location: round/outbox_messages.go:99, vtxo/outbox_messages.go:99

The PR description mentions "signature handling updated to use *schnorr.Signature objects directly" as a type safety improvement, but I notice the code uses *schnorr.Signature in some places:

// vtxo/outbox_messages.go:99
Signature *schnorr.Signature

And byte slices in others:

// round/transitions.go (forfeit signature handling)

Recommendation: Verify this is intentional and document why some places use *schnorr.Signature while others might use byte slices. This might be fine if one is for internal use and another for wire protocol, but it should be explicit.


6. Status Code Magic Numbers

Location: db/sqlc/queries/vtxo.sql:16, db/sqlc/migrations/000003_round_tables.up.sql:246-253

-- status tracks VTXO lifecycle (vtxo.VTXOStatus enum):
--   0 = Live (default)
--   1 = RefreshRequested
--   2 = Forfeiting
--   3 = Forfeited
--   4 = Spent
--   5 = Expiring
--   6 = Failed
status INTEGER NOT NULL DEFAULT 0,
WHERE status < 3

Issue: The query uses magic number 3 without explanation. While the migration has helpful comments, the query would benefit from inline documentation.

Recommendation: Add a comment explaining the threshold:

-- ListLiveVTXOs returns all VTXOs that are not in a terminal state.
-- Terminal states are: Forfeited (3), Spent (4), Expiring (5), Failed (6).
-- This is used during startup to recover active VTXO actors.
SELECT * FROM vtxos
WHERE status < 3  -- Live (0), RefreshRequested (1), Forfeiting (2)
ORDER BY creation_time DESC;

Actually, looking again, the comment is already there! This is fine as-is.


7. Code Duplication in Test Helpers

Location: vtxo/actor_test.go, round/transitions_test.go

As noted by the gemini-code-assist reviewer, there's a testSchnorrSignature helper function duplicated between test files. This should be moved to a shared test utility package like internal/testutils/ to avoid duplication.


Low Priority / Style Issues

8. Line Length and Formatting

Location: db/sqlc/schemas/generated_schema.sql

The generated schema file has status columns on a single line which impacts readability. While this is a generated file, consider if the generation settings can be adjusted to put each column on its own line for better git diffs and readability.


9. Error Log Level Usage

Location: Appears correct throughout

I checked the logging calls and they correctly follow the guideline:

Only use error level for internal errors never expected during normal operation.

The structured logging calls (ending in S) are properly used with static messages and key-value pairs. Good adherence to the style guide here.


Positive Observations

  1. Excellent use of BatchedTx pattern - VTXOPersistenceStore correctly uses the BatchedTx pattern for transaction safety
  2. Good separation of concerns - Actor messages, FSM transitions, and persistence are cleanly separated
  3. Type safety improvements - Using RoundID type and *schnorr.Signature instead of bare strings/bytes is great
  4. Comprehensive comments - Most functions have detailed comments explaining the "why" and "how"
  5. Proper structured logging - Good use of InfoS, DebugS with context and key-value pairs
  6. Good test coverage additions - The test files show proper testing of the new functionality

Testing Recommendations

Before merging, ensure:

  1. Run the test suite: make unit pkg=db case=VTXO
  2. Run integration tests: make itest icase=refresh (once refresh systests are added)
  3. Check logs carefully for any [ERR] lines that shouldn't be there
  4. Verify linting passes: make lint
  5. Test migration up/down: Verify the migration can be applied and rolled back cleanly

Summary of Required Changes

Must fix before merge:

  1. Address the spent VTXO resurrection bug (Critical multI: add initial repo scaffolding #2)
  2. Fix the wallet refresh handler false success issue (High chainbackend+chainsource: add chainsource actor and backend impl #3)

Should fix before merge:
3. Resolve the migration design per author's comment (Critical #1)
4. Add missing function comments or enhance existing ones (High #4)

Nice to have:
5. Move duplicate test helper to shared package (Medium #7)
6. Verify/document signature type consistency (Medium #5)

Overall this is solid work that lays important groundwork for refresh functionality. The main concerns are the database query bug and the false success response from the wallet handler, both of which could cause operational issues.


| Branch: roudn-forfeit-server-catch-up

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0cde8f441

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +246 to +250
-- status tracks VTXO lifecycle (vtxo.VTXOStatus enum):
-- 0 = Live (default)
-- 1 = RefreshRequested
-- 2 = Forfeiting
-- 3 = Forfeited

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add a forward migration for new vtxos columns

These new vtxos columns are introduced by editing migration 000003, but existing deployments that already ran 000003 will not re-run it, so their schema will still lack status/forfeit_* fields. All the new queries (e.g., ListLiveVTXOs, UpdateVTXOStatus) reference status and will start failing at runtime after upgrade. Please add a new forward migration (ALTER TABLE + backfill spent→status) instead of modifying a historical migration.

Useful? React with 👍 / 👎.

Comment thread db/vtxo_store.go
Comment on lines +368 to +372
return &vtxo.Descriptor{
Outpoint: outpoint,
Amount: btcutil.Amount(row.Amount),
PkScript: row.PkScript,
ClientKey: keychain.KeyDescriptor{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconstruct missing VTXO metadata on recovery

The recovered descriptor from rowToDescriptor only fills a subset of fields; TapScript, BatchExpiry, TreeDepth, and CreatedHeight remain zero/nil. After a restart, expiry checks in ExpiryConfig.CheckExpiry will treat BatchExpiry as expired (<=0), and signForfeitVTXOInput will fail because TapScript is nil, so refresh/forfeit flows break for any recovered actor. Consider persisting these fields or reconstructing them (e.g., from tree_path + round metadata) when loading.

Useful? React with 👍 / 👎.

Comment thread wallet/wallet.go Outdated
Comment on lines +543 to +546
a.roundActor.WhenSome(func(ref actor.TellOnlyRef[actormsg.RoundReceivable]) {
ref.Tell(ctx, &actormsg.TriggerVTXORefreshMsg{
TargetOutpoints: req.TargetOutpoints,
ForceRefresh: req.ForceRefresh,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor empty refresh requests by selecting expiring VTXOs

RefreshVTXOsRequest documents that an empty TargetOutpoints should refresh all VTXOs within the expiry threshold, but handleRefreshVTXOs forwards the empty list as-is. That means no VTXO actors are triggered and the response reports RefreshingCount as 0, so the documented behavior never happens. Consider querying the store for eligible VTXOs when the list is empty before forwarding.

Useful? React with 👍 / 👎.

@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch from d0cde8f to 828e638 Compare January 29, 2026 21:45
@Roasbeef
Roasbeef changed the base branch from round-actor-tests to main January 29, 2026 22:14
@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch from 828e638 to 407336f Compare January 29, 2026 22:14
In this commit, we change the forfeit signature type from raw []byte
to *schnorr.Signature across the forfeit flow types. This provides
type safety and consistency with the server's types.ForfeitTxSig
which uses *schnorr.Signature for ClientVTXOSig.

The following types are updated:
- ForfeitSignatureResponse.Signature (round/events.go)
- ForfeitSignatureSubmission.Signature (vtxo/outbox_messages.go)
- SubmitVTXOForfeitSigsToServer.ForfeitSigs map values
  (round/outbox_messages.go)

The signForfeitVTXOInput function now parses the serialized signature
into a typed *schnorr.Signature before returning. Test files add a
testSchnorrSignature helper that creates deterministic signatures
from a seed for consistent test behavior.
@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch 3 times, most recently from 9c51104 to 57a6af3 Compare January 31, 2026 01:57
Add migration 000004_vtxo_status to extend the vtxos table with status
tracking fields for the VTXO refresh flow:
- status: lifecycle state (Live, RefreshRequested, Forfeiting, etc.)
- forfeit_round_id: tracks the new round during refresh
- forfeit_tx: stores signed forfeit tx for crash recovery
- forfeit_txid: records confirmed forfeit transaction ID
- replaced_by_hash/index: links old VTXO to its replacement

Also adds vtxo.sql queries for status updates, forfeit tracking, and
VTXO retrieval by status.
Add VTXOPersistenceStore implementing vtxo.VTXOStore interface for
VTXO lifecycle persistence. The store uses the BatchedTx pattern for
transaction-safe operations and handles:

- SaveVTXO: persist new VTXOs with serialized tree paths
- GetVTXO/ListLiveVTXOs: retrieve VTXOs for actor recovery
- UpdateVTXOStatus: atomic status transitions
- MarkForfeiting: store forfeit tx for crash recovery
- MarkForfeited: record forfeit confirmation

Also updates migrations.go with the new migration and makes minor
adjustments to round_store.go for consistency.
Add wallet-level API messages for triggering VTXO refresh:

- RefreshVTXOsRequest: specifies target VTXOs to refresh with optional
  ForceRefresh flag to bypass expiry threshold checking
- RefreshVTXOsResponse: reports count of VTXOs queued for refresh and
  any errors encountered

These messages form the high-level interface for the refresh flow,
routing through the wallet actor to the round actor.
Add handler for RefreshVTXOsRequest that:
1. Retrieves target VTXOs from store (specific outpoints or all live)
2. Filters by expiry threshold unless ForceRefresh is set
3. Sends RefreshVTXORequest to round actor for each eligible VTXO

Also adds vtxoStore and roundActor dependencies to the wallet actor
config for refresh flow support.
Fix type consistency by using RoundID instead of string in the
SubmitVTXOForfeitSigsToServer message. This aligns with other round
messages that use the typed RoundID for better type safety.
Add TriggerRefreshEvent which is sent to VTXO actors to manually trigger
a refresh request. This bypasses the automatic expiry-based refresh and
immediately transitions the VTXO to RefreshRequested state.

The event is defined in round/vtxo_messages.go (where all VTXO actor
messages live) and type-aliased in vtxo/events.go. The LiveState handles
this event by emitting RefreshRequest to the round actor via outbox.

This enables user-initiated refresh through the wallet actor without
waiting for automatic expiry thresholds.
Add TriggerVTXORefreshMsg which is sent from the wallet actor to the
round actor to trigger refresh of specific VTXOs. This message is
defined in actormsg to avoid an import cycle between wallet and round
packages (round imports wallet for BoardingAddress).

The message implements RoundReceivable so it can be sent via the
wallet's TellOnlyRef[RoundReceivable] reference to the round actor.
Add handleTriggerVTXORefresh which processes TriggerVTXORefreshMsg from
the wallet actor. For each target outpoint, we look up the VTXO actor
via its service key and send TriggerRefreshEvent. The VTXO actors then
emit RefreshVTXORequest back to us through their outbox.

This completes the wallet -> round -> vtxo refresh triggering flow.
When handling RefreshVTXORequest events, create a corresponding
VTXORequest to ensure the refresh has an output destination. Without
this, refresh-only rounds would fail validation due to zero total
output amount.

Also add debug logging to both round and VTXO actors to aid in
tracing the forfeit request flow and state transitions during refresh
operations.
@Roasbeef
Roasbeef force-pushed the roudn-forfeit-server-catch-up branch from 57a6af3 to 117022e Compare January 31, 2026 02:40
@Roasbeef
Roasbeef merged commit a416dff into main Jan 31, 2026
16 checks passed
@bhandras
bhandras deleted the roudn-forfeit-server-catch-up branch February 20, 2026 16:22
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.

1 participant