Skip to content

CLTVMultisig integration test #32

Description

@louisinger

PR #30 added `CLTVMultisigClosure` support to `ReadArkadeScript` by replacing the hardcoded `MultisigClosure`-only decoder with a `scriptlib.DecodeClosure()` switch covering all closure types. We should add an e2e integration test exercising the `CLTVMultisigClosure` path end-to-end.


expanded by Arkana:

Background

Before PR #30, `pkg/arkade/script.go` had a `// TODO: allow any type of closure (condition, cltv ...)` comment and explicitly rejected anything that wasn't a `MultisigClosure`. PR #30 replaced that with a `scriptlib.DecodeClosure()`-based switch, adding handling for:

  • `MultisigClosure` (collaborative spend)
  • `CSVMultisigClosure` (unilateral exit — tracked separately in issue Unilateral exit integration test #31)
  • `CLTVMultisigClosure` ← this issue
  • `ConditionMultisigClosure`
  • `ConditionCSVMultisigClosure`

`CLTVMultisigClosure` encodes a script that is spendable only after an absolute block height or time (OP_CHECKLOCKTIMEVERIFY). This is useful for time-locked payment contracts, escrow with deadline, and scheduled VTXO redemptions.

What is CLTVMultisigClosure?

A `CLTVMultisigClosure` tapscript leaf enforces that:

  1. The spending transaction's `nLockTime` is ≥ the lock value in the script
  2. All required pubkeys have signed

This differs from `CSVMultisigClosure` (relative timelock, `nSequence`) in that CLTV uses an absolute lock, meaning the VTXO cannot be spent via this leaf until a specific block height or UNIX timestamp.

What needs to be tested

An integration test (suggested location: `test/cltv_test.go`, parallel to `test/tx_test.go` and `test/recursive_covenant_test.go`) that:

  1. Creates a VTXO whose tapscript tree includes a `CLTVMultisigClosure` leaf (e.g., locked until regtest block height + N)
  2. Submits an offchain tx spending via the CLTV leaf to introspector
  3. Verifies introspector's `ReadArkadeScript` decodes the `CLTVMultisigClosure`, extracts the owner pubkey, and produces a valid signature
  4. Confirms the signed tx is valid and broadcastable on regtest once the lock time is satisfied
  5. (Bonus) Confirms introspector correctly rejects the same tx if submitted before `nLockTime` is satisfied

Relevant code paths

  • `pkg/arkade/script.go` — `ReadArkadeScript` / `CLTVMultisigClosure` branch (added in PR Support every type of tapscript closure #30)
  • `pkg/arkade/script_test.go` — unit-level decode tests added in PR Support every type of tapscript closure #30 (reference for test data format)
  • `pkg/arkade/testdata/read_arkade_script.json` — test vectors for closure types
  • `test/tx_test.go` — `TestOffchain`: reference for full e2e test structure (setup, VTXO creation, introspector client, tx submission)
  • `test/utils_test.go` — `createVtxoScriptWithArkadeScript`, `setupIntrospectorClient`, regtest helpers
  • `arkd/pkg/ark-lib/script` — `CLTVMultisigClosure` definition (used via go-sdk dependency)

Key difference from CSVMultisigClosure (issue #31)

CSVMultisigClosure CLTVMultisigClosure
Timelock type Relative (`nSequence`) Absolute (`nLockTime`)
Bitcoin opcode OP_CHECKSEQUENCEVERIFY OP_CHECKLOCKTIMEVERIFY
Activation N blocks after VTXO creation Specific block height / timestamp
Use case Unilateral exit after aging Scheduled redemption / escrow

Why this matters

CLTV-locked VTXOs enable programmable expiry and time-gated redemption without server cooperation. If introspector silently fails to sign a CLTV leaf (e.g., due to a future refactor of the closure switch), any contract relying on CLTV redemption loses its execution path. This test guards that regression.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions