Skip to content
Open
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
11 changes: 9 additions & 2 deletions cmd/waved/taprootassets.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,17 @@ func configureTaprootAssets(cfg *waved.Config) {

return nil, err
}
reservationStore, err := rpcServer.OORReservationStore()
if err != nil {
closeWallet()

return nil, err
}
preparer, err := tapassets.NewPreparer(
tapassets.PreparerConfig{
Wallet: wallet,
Store: store,
Wallet: wallet,
Store: store,
ReservationStore: reservationStore,
},
)
if err != nil {
Expand Down
13 changes: 7 additions & 6 deletions db/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/db.<Symb
- `PendingIntentStore` / `BatchedPendingIntentStore` — internal sqlc-backed
query interfaces for the pending-intent tables.
- `SpendingReservationPersistenceStore` — Persists the durable index of VTXO
outpoints reserved by an active spend owner (e.g. an outgoing OOR session).
A row exists IFF the owning session was durably checkpointed, so a startup
sweep can deterministically release orphaned Spending VTXOs with no row.
outpoints reserved by an active spend owner (a pre-commit Taproot Asset
preparation or outgoing OOR session). A row exists after the owner crosses
its durable handoff boundary, so a startup sweep can deterministically
release orphaned Spending VTXOs with no row.
Methods: `UpsertReservation(ctx, outpoint, ownerKind, ownerID)` (upserts a
row), `ListReservedOutpoints(ctx)` (returns all reserved outpoints for the
startup sweep). Implements both `oor.ReservationStore` and
Expand Down Expand Up @@ -175,9 +176,9 @@ when adding one.
`(swap_id, action, vtxo_txid, vtxo_vout)` so a refreshed vHTLC (new
outpoint) arms a new recovery generation instead of colliding with
the prior job.
- `000008_intents` — `spending_reservations` (a row exists IFF the
owning spend session was durably checkpointed; supports the startup
orphan sweep in `vtxo.Manager.sweepOrphanedReservations`) plus the
- `000008_intents` — `spending_reservations` (a row exists after a Taproot
Asset preparation is quarantined or an OOR session is checkpointed; supports
the startup orphan sweep in `vtxo.Manager.sweepOrphanedReservations`) plus the
pending-intent outbox supertype/subtype set: `pending_intent_kinds`,
`pending_intents` header, `pending_board_intents` /
`pending_send_intents` detail tables, and `pending_intent_anchors`.
Expand Down
13 changes: 7 additions & 6 deletions db/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/db.<Symb
- `PendingIntentStore` / `BatchedPendingIntentStore` — internal sqlc-backed
query interfaces for the pending-intent tables.
- `SpendingReservationPersistenceStore` — Persists the durable index of VTXO
outpoints reserved by an active spend owner (e.g. an outgoing OOR session).
A row exists IFF the owning session was durably checkpointed, so a startup
sweep can deterministically release orphaned Spending VTXOs with no row.
outpoints reserved by an active spend owner (a pre-commit Taproot Asset
preparation or outgoing OOR session). A row exists after the owner crosses
its durable handoff boundary, so a startup sweep can deterministically
release orphaned Spending VTXOs with no row.
Methods: `UpsertReservation(ctx, outpoint, ownerKind, ownerID)` (upserts a
row), `ListReservedOutpoints(ctx)` (returns all reserved outpoints for the
startup sweep). Implements both `oor.ReservationStore` and
Expand Down Expand Up @@ -175,9 +176,9 @@ when adding one.
`(swap_id, action, vtxo_txid, vtxo_vout)` so a refreshed vHTLC (new
outpoint) arms a new recovery generation instead of colliding with
the prior job.
- `000008_intents` — `spending_reservations` (a row exists IFF the
owning spend session was durably checkpointed; supports the startup
orphan sweep in `vtxo.Manager.sweepOrphanedReservations`) plus the
- `000008_intents` — `spending_reservations` (a row exists after a Taproot
Asset preparation is quarantined or an OOR session is checkpointed; supports
the startup orphan sweep in `vtxo.Manager.sweepOrphanedReservations`) plus the
pending-intent outbox supertype/subtype set: `pending_intent_kinds`,
`pending_intents` header, `pending_board_intents` /
`pending_send_intents` detail tables, and `pending_intent_anchors`.
Expand Down
9 changes: 5 additions & 4 deletions db/spending_reservation_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ type BatchedSpendingReservationStore interface {
}

// SpendingReservationPersistenceStore persists the durable index of VTXO
// outpoints reserved by an active spend owner (e.g. an outgoing OOR session).
// A row exists IFF the owning session was durably checkpointed, so a startup
// sweep can deterministically release orphaned Spending VTXOs that have no
// reservation row.
// outpoints reserved by an active spend owner, such as a Taproot Asset
// preparation or outgoing OOR session. A row exists only after the owner has
// crossed its durable handoff boundary, so a startup sweep can
// deterministically release orphaned Spending VTXOs that have no reservation
// row.
type SpendingReservationPersistenceStore struct {
db BatchedSpendingReservationStore
clock clock.Clock
Expand Down
8 changes: 4 additions & 4 deletions db/sqlc/migrations/000008_intents.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
-- user requests across a daemon restart.

-- spending_reservations is a durable index of VTXO outpoints currently held
-- in SpendingState by an active spend owner (e.g. an outgoing OOR session).
-- A row exists IFF the owning session was durably checkpointed, so a startup
-- sweep can deterministically identify orphaned Spending VTXOs (those with no
-- reservation row) and release them.
-- in SpendingState by an active spend owner. A row exists after the owning
-- workflow crosses its durable handoff boundary (pre-commit Taproot Asset
-- preparation or checkpointed OOR session), so a startup sweep can identify
-- orphaned Spending VTXOs (those with no reservation row) and release them.
CREATE TABLE IF NOT EXISTS spending_reservations (
-- outpoint_hash identifies the reserved VTXO outpoint. The 32-byte
-- length check rejects truncated or malformed hashes at the DB layer.
Expand Down
9 changes: 5 additions & 4 deletions db/sqlc/querier.go

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

4 changes: 2 additions & 2 deletions db/sqlc/queries/spending_reservations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

-- name: UpsertSpendingReservation :exec
-- UpsertSpendingReservation records (or refreshes) the reservation for one
-- outpoint. The owner fields are updated on conflict so a re-checkpointed
-- session re-binds the same outpoint to its current owner.
-- outpoint. The owner fields are updated on conflict so a resumed workflow
-- re-binds the same outpoint to its current durable owner.
INSERT INTO spending_reservations (
outpoint_hash, outpoint_index, owner_kind, owner_id, created_at
) VALUES (
Expand Down
7 changes: 4 additions & 3 deletions db/sqlc/queries/vtxo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ ORDER BY vtxos.creation_time DESC;

-- name: ListVTXOSelectionCandidatesByStatus :many
-- ListVTXOSelectionCandidatesByStatus returns the lightweight projection coin
-- selection runs on: outpoint, amount, and pkScript. Selection happens on
-- every payment and only needs these three fields, so this avoids decoding
-- selection runs on: outpoint, amount, pkScript, and the optional Taproot
-- Asset root. Selection happens on every payment and only needs these fields,
-- so this avoids decoding
-- full descriptors (pubkey parsing, taproot script reconstruction, policy
-- template decode) and the batched ancestry-path query on the hot path.
SELECT outpoint_hash, outpoint_index, amount, pk_script
SELECT outpoint_hash, outpoint_index, amount, pk_script, taproot_asset_root
FROM vtxos
WHERE status = $1 AND taproot_asset_root IS NULL
ORDER BY creation_time DESC;
Expand Down
4 changes: 2 additions & 2 deletions db/sqlc/spending_reservations.sql.go

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

17 changes: 10 additions & 7 deletions db/sqlc/vtxo.sql.go

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

28 changes: 21 additions & 7 deletions db/vtxo_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,11 @@ func (s *VTXOPersistenceStore) rowsToDescriptorsNoAncestry(ctx context.Context,
}

// ListSelectionCandidatesByStatus returns the lightweight projection coin
// selection runs on: outpoint, amount, and pkScript per VTXO in the given
// status. Selection happens on every payment and needs only these fields, so
// this path skips the full descriptor decode (pubkey parsing, taproot script
// reconstruction, policy template decode) and the batched ancestry query
// entirely.
// selection runs on: outpoint, amount, pkScript, and the optional Taproot
// Asset root per VTXO in the given status. Selection happens on every payment
// and needs only these fields, so this path skips the full descriptor decode
// (pubkey parsing, taproot script reconstruction, policy template decode) and
// the batched ancestry query entirely.
func (s *VTXOPersistenceStore) ListSelectionCandidatesByStatus(
ctx context.Context, status vtxo.VTXOStatus) ([]vtxo.SelectedVTXO,
error) {
Expand All @@ -467,14 +467,28 @@ func (s *VTXOPersistenceStore) ListSelectionCandidatesByStatus(
for _, row := range rows {
var outpointHash chainhash.Hash
copy(outpointHash[:], row.OutpointHash)
var taprootAssetRoot *chainhash.Hash
if len(row.TaprootAssetRoot) > 0 {
if len(row.TaprootAssetRoot) !=
chainhash.HashSize {
return fmt.Errorf("invalid selection "+
"Taproot Asset root length: %d",
len(row.TaprootAssetRoot))
}

root := &chainhash.Hash{}
copy(root[:], row.TaprootAssetRoot)
taprootAssetRoot = root
}

candidates = append(candidates, vtxo.SelectedVTXO{
Outpoint: wire.OutPoint{
Hash: outpointHash,
Index: uint32(row.OutpointIndex),
},
Amount: btcutil.Amount(row.Amount),
PkScript: row.PkScript,
Amount: btcutil.Amount(row.Amount),
PkScript: row.PkScript,
TaprootAssetRoot: taprootAssetRoot,
})
}

Expand Down
22 changes: 13 additions & 9 deletions db/vtxo_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,43 +201,46 @@ func TestUpdateVTXOStatusReleasingReservation(t *testing.T) {
)

ownerID := chainhash.Hash{0x99}
const taprootAssetPreparationOwnerKind = 1
require.NoError(
t, reservationStore.UpsertReservation(
ctx, desc.Outpoint, 0, ownerID,
ctx, desc.Outpoint, taprootAssetPreparationOwnerKind,
ownerID,
),
)

reserved, err := reservationStore.ListReservedOutpoints(ctx)
require.NoError(t, err)
require.Equal(t, []wire.OutPoint{desc.Outpoint}, reserved)

// The combined update flips the status to Spent and drops the row in
// one transaction.
// A known-safe setup failure releases the VTXO to Live and drops the
// preparation-owner row in one transaction.
require.NoError(
t, vtxoStore.UpdateVTXOStatusReleasingReservation(
ctx, desc.Outpoint, vtxo.VTXOStatusSpent,
ctx, desc.Outpoint, vtxo.VTXOStatusLive,
),
)

fetched, err := vtxoStore.GetVTXO(ctx, desc.Outpoint)
require.NoError(t, err)
require.Equal(t, vtxo.VTXOStatusSpent, fetched.Status)
require.Equal(t, vtxo.VTXOStatusLive, fetched.Status)

reserved, err = reservationStore.ListReservedOutpoints(ctx)
require.NoError(t, err)
require.Empty(t, reserved)

// Releasing a reservation for an outpoint that has none is a no-op: the
// status still updates and no error surfaces.
// Updating an outpoint whose reservation was already released is a
// no-op for the reservation table: the status still updates without an
// error.
require.NoError(
t, vtxoStore.UpdateVTXOStatusReleasingReservation(
ctx, desc.Outpoint, vtxo.VTXOStatusLive,
ctx, desc.Outpoint, vtxo.VTXOStatusSpent,
),
)

fetched, err = vtxoStore.GetVTXO(ctx, desc.Outpoint)
require.NoError(t, err)
require.Equal(t, vtxo.VTXOStatusLive, fetched.Status)
require.Equal(t, vtxo.VTXOStatusSpent, fetched.Status)
}

// TestVTXOPersistenceStoreSaveAndGet tests the basic save and get operations.
Expand Down Expand Up @@ -395,6 +398,7 @@ func TestListSelectionCandidatesByStatus(t *testing.T) {
require.True(t, ok)
require.Equal(t, desc.Amount, candidate.Amount)
require.Equal(t, desc.PkScript, candidate.PkScript)
require.Nil(t, candidate.TaprootAssetRoot)
}

storedAsset, err := vtxoStore.GetVTXO(ctx, descAsset.Outpoint)
Expand Down
Loading