Skip to content

OOR client 3/4: durable actor, TLV codecs, signing context - #79

Merged
bhandras merged 13 commits into
mainfrom
oor-client-split-3
Feb 19, 2026
Merged

OOR client 3/4: durable actor, TLV codecs, signing context#79
bhandras merged 13 commits into
mainfrom
oor-client-split-3

Conversation

@bhandras

@bhandras bhandras commented Jan 12, 2026

Copy link
Copy Markdown
Member

Context

Third client PR in the OOR stack. Split 2 (#78) landed the outgoing/incoming FSMs and the actor wrapper. This split makes the actor durable: every command, FSM state, and signing artifact survives a restart so in-flight transfers resume exactly where they left off.

What changes

Tx primitive reorganization

lib/tx/oor is split into focused packages (arktx, checkpoint, psbtutil) and a versioned submit-package encoding is added so OOR payloads have a stable binary format for persistence and replay.

VTXO signing context

Transfer inputs now carry full VTXO descriptors (keys, scripts, expiry) instead of raw checkpoint-only data. This context is threaded through FSM states so checkpoint signing, outbox events, and crash-resume snapshots all have access to it without a separate lookup path.

TLV durability codecs

JSON command/snapshot serialization is replaced with typed TLV records. Start, session, restore, and drive-event payloads each get their own encode/decode path with overflow-checked numeric conversions and length-scoped leaf decoding.

Durable actor plumbing

  • Restart enqueue uses epoch timestamps so ordering is clock-independent.
  • DriveEventRequest is persisted through the command codec.
  • Session identity is bound to the proposed spend; duplicate session IDs are rejected on restore.
  • Outbox events emit typed protobuf Any envelopes with explicit error variants.

Hardening and docs

  • TLV numeric overflow checks on decode, taptree leaf boundary scoping.
  • Exported-type GoDocs across the OOR and tx helper packages.

Client durability model

Two persistence scopes with distinct ownership:

1) Runtime durability (generic actor delivery)

Owned by db/actordelivery and baselib/actor.DurableActor:

  • Durable inbox (mailbox_messages)
  • Processing leases + ack/nack/redelivery
  • Dedup markers (processed_messages)
  • Ask result persistence (ask_results)
  • Actor checkpoint row (fsm_checkpoints)
  • Dead-letter handling (dead_letters)

2) OOR session snapshot durability (checkpoint payload)

Owned by OOR durable behavior in oor/actor.go:

  • Per-session outgoing FSM state exported into OutgoingSnapshot
  • Session map stored in checkpoint payload
  • Restart reconstructs in-memory session handles from checkpoint data

Persistence map

Concern Storage Encoding Written at Read at
Durable actor message mailbox_messages.payload TLV enqueue/send durable actor receive loop
OOR durable command envelope message payload bytes TLV Receive dispatch command decode
Start/get/resume/export/restore payloads command payload bytes TLV command construction command decode
OOR session checkpoint fsm_checkpoints.state_data TLV post-handle actor startup/restart
Outgoing PSBT/session artifacts checkpoint payload TLV + PSBT bytes snapshot export snapshot restore

Commits

  1. tx: split arktx and checkpoint primitives
  2. tx: add PSBT and package encodings
  3. oor: add VTXO signing context
  4. oor: add TLV codec for durable actor command payloads
  5. oor: validate TLV numeric conversions on decode
  6. oor: stabilize start-session contract and session ID binding
  7. oor: implement outbox proto envelopes
  8. actor: make restart enqueue independent of wall clock
  9. oor: make DriveEventRequest durable
  10. test: improve OOR primitive coverage
  11. tx: harden taptree TLV decoding boundaries
  12. oor: add GoDocs for exported types
  13. oor: document TransferInputs FSM threading rationale

Not included

Stack

Parent: #78 · Next: #81

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @bhandras, 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 improves the robustness and reliability of the OOR client by enhancing its test coverage. It focuses on critical areas such as crash-resume capabilities, ensuring that client sessions can be seamlessly restored even after unexpected interruptions. Additionally, the changes extend the testing of the client's state machine to cover various edge cases, including how it reacts to out-of-band events and handles communication errors, thereby making the client more resilient to failures and unexpected scenarios.

Highlights

  • Client Crash-Resume Coverage: New test cases have been added to validate the OOR client's ability to resume operations after a crash, particularly when the server has already co-signed a transaction but the client's response was not received, relying solely on persisted snapshots.
  • FSM Edge-Case Test Extension: The test suite for the client's Finite State Machine (FSM) has been expanded to cover more edge cases, including how terminal states handle unexpected events and the deterministic handling of outbox errors for retries or terminal failures.
  • Drive Event Request Testing: Comprehensive tests were introduced for the DriveEventRequest handler, ensuring it correctly processes valid events to advance session states and rejects malformed inputs.

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

@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 significantly improves the test coverage for the OOR client, focusing on crucial crash-resume scenarios and FSM edge cases. The new tests in oor/actor_drive_event_test.go, oor/coverage_misc_test.go, and oor/outbox_error_test.go are well-structured and cover important functionality like external event injection, terminal state behavior, and outbox error handling. The addition of TestOORClientActorResumeAfterServerCoSignedFromStore in oor/actor_resume_test.go is particularly valuable as it verifies the client's ability to recover from a persisted state after a crash. My main suggestion is to refactor some duplicated test setup code to improve maintainability. Overall, these changes greatly enhance the robustness and reliability of the client actor.

Comment thread oor/actor_resume_test.go
@bhandras
bhandras marked this pull request as draft January 12, 2026 18:26
@bhandras bhandras changed the title OOR client tests OOR[3/3] client tests Jan 12, 2026
@bhandras bhandras changed the title OOR[3/3] client tests OOR client split 3: tx split + signing + resume Jan 13, 2026
@bhandras
bhandras marked this pull request as ready for review January 30, 2026 14:47
@litbot-9000

Copy link
Copy Markdown
Collaborator

@bhandras, remember to re-request review from reviewers when ready

Comment thread lib/tx/arktx/canonical.go
@@ -0,0 +1,215 @@
package arktx

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.

The existing lib/tx/oor package keeps its public API stable via thin
wrappers and aliases, so server/client call sites (and tests) continue
to work while we migrate incrementally.

just checking (maybe answered later): but think we dont have to keep things backwards compatible rn and can just do big refactors (i know the agents like to do this bw compat thing)

@bhandras bhandras Feb 6, 2026

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.

Updated: transitional OOR canonical wrappers were removed and call sites now use lib/tx/arktx directly, so there is no extra compatibility layer left here.

@ellemouton ellemouton 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.

nice!

Comment thread oor/actor_messages.go Outdated
Comment thread oor/states.go Outdated

// AwaitingArkConfirmation indicates the server accepted finalize and the
// client is optionally waiting for the Ark tx to confirm on-chain.
type AwaitingArkConfirmation struct {

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.

still making way through diff but at this point not entirely sure i follow why we need this - does that mean the client is expected to unroll?

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.

in my mind, stronger semantics would perhaps mean "send via batch swap"

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.

This is mainly for durability/unilateral-exit readiness: client needs enough artifacts to recover path construction after restart.

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.

You were right to question this — that was a design mistake on our side.

We initially introduced an AwaitingArkConfirmation phase (WaitForArkConfirmation / ArkConfirmDepth) while shaping restart semantics, but this does not belong in the normal collaborative OOR flow. It made the state model noisier without a clear protocol requirement, so we removed it from split 3.

Current model no longer includes that phase or fields.

Ref: d897a4b

@bhandras
bhandras force-pushed the oor-client-split-3 branch 2 times, most recently from 3d1da3a to d37031c Compare February 5, 2026 12:20
@bhandras
bhandras force-pushed the oor-client-split-3 branch 7 times, most recently from 5c293ea to 4735dfd Compare February 17, 2026 14:58
@bhandras
bhandras requested a review from ellemouton February 17, 2026 15:27
@bhandras
bhandras marked this pull request as ready for review February 17, 2026 15:27
Comment thread lib/tx/oor/package.go Outdated
Comment on lines +24 to +26
submitPackageVersionRecordType tlv.Type = 1
submitPackageArkPSBTRecordType tlv.Type = 3
submitPackageCheckpointsRecordType tlv.Type = 5

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.

just noting that we dont do this skipping of numbers on server side

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.

Fixed: TLV record types now use sequential numbering (1, 2, 3) matching the server-side convention.

Comment thread oor/checkpoint_sign.go
// Each checkpoint PSBT is expected to spend exactly one VTXO (input index 0).
// The TransferInput slice is expected to match the checkpoint PSBT slice
// 1:1.
func SignCheckpointPSBTs(signer input.Signer, inputs []TransferInput,

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.

can the server not also make use of this rather than re-defining it?

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.

Good call. The helpers (AddTapLeafScript, AddTaprootScriptSpendSig) are now in lib/tx/psbtutil so both client and server can use them. The server-side dedup (replacing its local copies with imports from psbtutil) is tracked in darepo#91.

Comment thread oor/states.go

// TransferInputs are the vtxo descriptors and scripts needed later on
// to sign the checkpoint PSBTs.
TransferInputs []TransferInput

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.

does the FSM itself actually need this? isnt it only used for signing (ie at the actor level)?

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.

You're right — the FSM transitions don't use it. It's threaded through the states so the actor-level outbox events (which need the signing context) and checkpoint snapshots (which capture it for crash-resume) can access it without a separate lookup path. Added a comment on both states explaining this rationale.

Comment thread oor/actor_durable_message.go Outdated
)

const (
oorDurableCommandTLVType tlv.Type = 42001

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.

reason behind the large type number?

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.

Fixed: renamed to 0x7003 with a doc comment explaining the high range avoids collisions with the actor framework's reserved types (e.g. actor.RestartTLVType). The old 42001 was arbitrary.

Comment thread oor/transfer_input_snapshot.go Outdated
// deterministically, without serializing Go wallet types directly.
type TransferInputSnapshot struct {
// Outpoint is the VTXO outpoint being transferred.
Outpoint wire.OutPoint `json:"outpoint"`

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.

why do we need the json tags if we are using tlv?

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.

Fixed: JSON tags removed. They were leftover from an earlier iteration before the TLV encoding was in place.

Comment thread oor/actor.go
@@ -3,11 +3,19 @@ package oor
import (

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.

code here could use some docs!

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.

Fixed: added godoc comments to oorDurableBehavior, Receive, handleRestart, restoreFromCheckpoint, resumeRestoredSessions, and persistCheckpoint.

Comment thread oor/actor.go

actorID string

sessions map[SessionID]*sessionHandle

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.

never cleaned up i think?

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.

Good catch — removed. actorID was redundant with cfg.ActorID. All references now go through b.cfg.ActorID.

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/anypb"
)

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.

godocs missing for this commit and before

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.

Fixed: added godoc comments to all test functions in this file.

Move Ark tx canonicalization and checkpoint-building helpers out of
`lib/tx/oor` into dedicated `lib/tx/arktx` and `lib/tx/checkpoint`
packages.

Keep compatibility wrappers in `lib/tx/oor` so existing call sites and
current tests continue to compile while the stack migrates.
Add `lib/tx/psbtutil` helpers for serializing and parsing PSBT packets.

Introduce a versioned submit-package encoding in `lib/tx/oor` so OOR
payloads have a stable, transport-independent format for persistence and
replay.
Refactor outgoing transfer inputs to carry full VTXO signing descriptors
(keys, scripts, and expiry) instead of raw checkpoint-only inputs.

Add checkpoint-signing helpers that consume this context and add
incoming materialization helpers so receive-side flow can derive local
VTXO artifacts from Ark outputs.
Replace JSON command payload serialization with typed TLV records and
length-prefixed vectors for start and session durability envelopes.

Add transfer-input snapshot payload encoders/decoders and focused
round-trip tests for durable actor command paths.
Add explicit overflow checks when converting decoded TLV numeric values
to signed and narrower integer types.

Fail fast on malformed payloads instead of silently wrapping values,
with coverage for actor messages and outgoing snapshot codecs.
Bind outgoing sessions to a canonical identity derived from the
proposed spend and keep the start-session contract consistent at actor
boundaries.

Reject duplicate session IDs during restore to prevent malformed durable
payloads from replacing an existing in-memory session.
Replace placeholder `ToProto` implementations for submit, finalize,
mark-inputs-spent, and incoming-ack outbox events.

Emit typed `Any` envelopes with deterministic binary payloads and
explicit `*.error` envelopes, then add tests asserting type URLs and
non-empty payload bytes.
Set restart-envelope `available_at` to the epoch in
`PrependRestartMessage` instead of using `time.Now()`.

This preserves restart priority while avoiding ordering drift when the
delivery store clock differs from wall clock in tests or runtime.
Persist DriveEventRequest messages through the durable actor command
codec so externally driven transitions survive restarts.

Harden SubmitAcceptedEvent handling by requiring the request session,
event session, and Ark txid-derived session identity to match.
Add focused unit coverage for OOR primitive helpers: checkpoint build
artifacts, submit-package wrapper methods, PSBT utility codecs, outgoing
snapshot transitions, and transfer-input snapshot round-trips.

This expands confidence in the low-level tx/persistence helpers that the
actor durability flow depends on.
Decode each tap tree leaf from its length-scoped reader so unknown TLV
records do not consume bytes from adjacent records.

Add a regression test that appends an extra odd TLV record to verify
known fields still decode correctly.
Add package-level docs and exported type/field comments across the OOR
transfer stack and tx helper packages.

This aligns public API documentation with repo style requirements and
keeps durability-related types easier to review.
Add design comments explaining why TransferInputs are carried on
FSM states rather than managed at the actor boundary. The signing
context is threaded through states so the FSM can emit complete
outbox events and checkpoint snapshots can capture it for
crash-resume.
@bhandras bhandras changed the title OOR client 3/4: tx package split + signing + snapshot/resume OOR client 3/4: durable actor, TLV codecs, signing context Feb 19, 2026
@bhandras
bhandras merged commit 6421369 into main Feb 19, 2026
45 of 46 checks passed
@bhandras
bhandras deleted the oor-client-split-3 branch February 20, 2026 16:20
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.

3 participants