|
| 1 | +--- |
| 2 | +title: Redeemer Indexing |
| 3 | +description: Deferred redeemer construction for efficient validator input lookup |
| 4 | +--- |
| 5 | + |
| 6 | +import { Mermaid } from "@/components/mdx/mermaid" |
| 7 | + |
| 8 | +## Abstract |
| 9 | + |
| 10 | +Plutus validators can receive input indices via redeemers for O(1) array lookup instead of O(n) traversal. On-chain traversal is expensive in execution units, so passing indices allows validators to directly access only the inputs they care about. |
| 11 | + |
| 12 | +The challenge: Cardano currently enforces canonical sorting of inputs, and indices depend on this final sorted order. When coin selection or other operations add inputs to the transaction, they insert into the sorted order and shift existing indices. |
| 13 | + |
| 14 | +The solution: **defer redeemer construction** until all inputs are known and sorted. |
| 15 | + |
| 16 | +> **Note**: [CIP-128](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0128) proposes removing the canonical ordering requirement, allowing user-controlled input order. Once implemented, you could place contract inputs at known positions (e.g., indices 0, 1, 2) and append wallet inputs after, making indices predictable without deferred construction. |
| 17 | +
|
| 18 | +## Why Index-Based Lookup Matters |
| 19 | + |
| 20 | +Cardano transactions sort inputs lexicographically by `(txHash, outputIndex)`. A validator receives all transaction inputs in this canonical order. Without indices, finding relevant inputs requires traversing the entire list. |
| 21 | + |
| 22 | +<Mermaid chart={` |
| 23 | +graph LR |
| 24 | + subgraph Traversal["Without Indices: O(n)"] |
| 25 | + T1[For each input] --> T2[Check if relevant] --> T3[Process if match] |
| 26 | + end |
| 27 | + |
| 28 | + subgraph Direct["With Indices: O(1)"] |
| 29 | + D1[Redeemer contains indices] --> D2["Access inputs[i] directly"] --> D3[Process immediately] |
| 30 | + end |
| 31 | +`} /> |
| 32 | + |
| 33 | +Consider a transaction with 15 inputs: 5 contract UTxOs and 10 wallet UTxOs for fees. A naive validator would check all 15 inputs to find the 5 it cares about. With indices in the redeemer, it directly accesses `inputs[3]`, `inputs[7]`, `inputs[12]`, etc. |
| 34 | + |
| 35 | +## The Stake Validator Pattern |
| 36 | + |
| 37 | +A common pattern uses a stake validator as the main coordinator. Spend validators are "dumb" and only verify that the stake validator ran. The stake validator receives all relevant input indices and performs the actual business logic. |
| 38 | + |
| 39 | +<Mermaid chart={` |
| 40 | +graph TD |
| 41 | + subgraph Transaction |
| 42 | + SW[Stake Validator Withdrawal] |
| 43 | + S1[Spend Input 1] |
| 44 | + S2[Spend Input 2] |
| 45 | + S3[Spend Input 3] |
| 46 | + W1[Wallet Input] |
| 47 | + W2[Wallet Input] |
| 48 | + end |
| 49 | + |
| 50 | + subgraph Validation |
| 51 | + SW -->|"redeemer: [0, 2, 4]"| Logic[Business Logic] |
| 52 | + Logic -->|"inputs[0]"| S1 |
| 53 | + Logic -->|"inputs[2]"| S2 |
| 54 | + Logic -->|"inputs[4]"| S3 |
| 55 | + S1 -->|check withdrawal exists| SW |
| 56 | + S2 -->|check withdrawal exists| SW |
| 57 | + S3 -->|check withdrawal exists| SW |
| 58 | + end |
| 59 | +`} /> |
| 60 | + |
| 61 | +The stake validator: |
| 62 | +1. Receives indices of contract inputs in its redeemer |
| 63 | +2. Directly accesses those inputs via index |
| 64 | +3. Validates business logic for all contract inputs in one execution |
| 65 | + |
| 66 | +The spend validators: |
| 67 | +1. Simply verify the stake validator ran (withdrawal exists in transaction) |
| 68 | +2. Delegate all logic to the stake validator |
| 69 | + |
| 70 | +This pattern reduces total execution cost since the stake validator runs once instead of N spend validators running independently. |
| 71 | + |
| 72 | +## The Circular Dependency |
| 73 | + |
| 74 | +The problem: input indices depend on the final sorted order of **all** inputs. But coin selection adds wallet UTxOs after the user specifies script inputs. The indices change. |
| 75 | + |
| 76 | +<Mermaid chart={` |
| 77 | +graph LR |
| 78 | + P1["User specifies<br/>script inputs"] --> P2["Needs indices<br/>for redeemer"] |
| 79 | + P2 -.->|"depends on"| P4 |
| 80 | + P3["Transaction built"] --> P4["Coin selection<br/>adds wallet UTxOs"] |
| 81 | + P4 --> P5["Inputs sorted<br/>indices known"] |
| 82 | + P5 --> P6["Too late!<br/>Redeemer built"] |
| 83 | +`} /> |
| 84 | + |
| 85 | +## Deferred Construction |
| 86 | + |
| 87 | +Instead of building the redeemer immediately, store a **builder function** that will be invoked after coin selection: |
| 88 | + |
| 89 | +<Mermaid chart={` |
| 90 | +sequenceDiagram |
| 91 | + participant User |
| 92 | + participant Builder |
| 93 | + participant CoinSelection |
| 94 | + participant Resolver |
| 95 | +
|
| 96 | + User->>Builder: Specify inputs with redeemer function |
| 97 | + Note over Builder: Store function, don't execute |
| 98 | +
|
| 99 | + User->>Builder: build() |
| 100 | + Builder->>CoinSelection: Run selection |
| 101 | + CoinSelection-->>Builder: Wallet UTxOs added |
| 102 | +
|
| 103 | + Builder->>Resolver: All inputs now known |
| 104 | + Resolver->>Resolver: Sort inputs canonically |
| 105 | + Resolver->>Resolver: Compute indices |
| 106 | + Resolver->>Resolver: Invoke redeemer function with indices |
| 107 | + Resolver-->>Builder: Concrete redeemer returned |
| 108 | +
|
| 109 | + Builder-->>User: Transaction ready |
| 110 | +`} /> |
| 111 | + |
| 112 | +## Three Modes |
| 113 | + |
| 114 | +### Batch Mode |
| 115 | + |
| 116 | +For validators that need indices of multiple inputs. The function receives all indexed inputs and returns a single redeemer. |
| 117 | + |
| 118 | +<Mermaid chart={` |
| 119 | +graph TD |
| 120 | + B1[Specify inputs to track] --> B2[Function called once after sorting] --> B3["Receives all { index, utxo } pairs"] --> B4[Returns redeemer with indices list] |
| 121 | +`} /> |
| 122 | + |
| 123 | +Primary use case: stake validator coordinator receiving list of contract input indices. |
| 124 | + |
| 125 | +### Self Mode |
| 126 | + |
| 127 | +For spend validators that need their own index. The function is called once per script UTxO. |
| 128 | + |
| 129 | +<Mermaid chart={` |
| 130 | +graph TD |
| 131 | + SE1[Each script UTxO] --> SE2[Function called per UTxO] --> SE3["Receives own { index, utxo }"] --> SE4[Returns redeemer with own index] |
| 132 | +`} /> |
| 133 | + |
| 134 | +Use case: spend validator that looks up its own input by index for validation. |
| 135 | + |
| 136 | +### Static Mode |
| 137 | + |
| 138 | +For redeemers that don't need indices. The data is used directly. |
| 139 | + |
| 140 | +<Mermaid chart={` |
| 141 | +graph TD |
| 142 | + S1[Concrete redeemer data] --> S2[No computation needed] --> S3[Used directly] |
| 143 | +`} /> |
| 144 | + |
| 145 | +## Indexed Input |
| 146 | + |
| 147 | +Both Batch and Self modes receive indexed inputs containing: |
| 148 | + |
| 149 | +- **Index**: Final 0-based position in canonically sorted transaction inputs |
| 150 | +- **UTxO**: The original UTxO data |
| 151 | + |
| 152 | +The UTxO is included because redeemers may need output reference data, not just the index. |
| 153 | + |
| 154 | +## When to Use Each Mode |
| 155 | + |
| 156 | +| Mode | Use Case | |
| 157 | +|------|----------| |
| 158 | +| **Batch** | Stake validator coordinator with list of contract input indices | |
| 159 | +| **Self** | Spend validator needing its own index | |
| 160 | +| **Static** | Redeemer doesn't depend on indices | |
| 161 | + |
| 162 | +## Related Topics |
| 163 | + |
| 164 | +- [Deferred Execution](/docs/architecture/deferred-execution) - Program storage and fresh state model |
| 165 | +- [Transaction Flow](/docs/architecture/transaction-flow) - Build phases and state machine |
0 commit comments