Transaction chaining refers to the process of merging transactions that have a predecessor-successor relationship, meaning that some of the outputs of the predecessor are being spent as inputs by the successor. Cut-through is the process of removing those edges from the resulting transaction.
Neptune Cash does not presently support transaction chaining. Every transaction must spend confirmed inputs only. You simply cannot construct a transaction that spends the outputs of another transaction if that other transaction has not been confirmed yet. Mergers are allowed only if the two operands spend disjoint sets of UTXOs.
With an eye to decentralized finance, the inability to chain transactions is a particularly irksome limitation. It means that every block can support at most one interaction with a self-perpetuating UTXO (also known as smart contract). The name-giving output of that self-perpetuating UTXO, which contains the smart contract’s updated state, cannot be spent before it is confirmed. Phrased crudely, you get an on-chain uniswap throughput capped at one trade per block.
This post proposes a change to the transaction initiation pipeline in order to enable transaction chaining. Along the way, another irksome limitation of the current transaction initiation pipeline is fixed: the relatively high cost of the Raise operation which is what sends ProofCollection-backed transaction to a SingleProof-backed one.
Note: I wrote this note completely unaware that I had written a similar one only half a year ago. I decided not to merge the two in order to benefit from independent perspectives. Before proceeding with an implementation, it’s probably worth cross-comparing and weighing the differences and noting the similarities.
Dual Pipeline
Duplicate the transaction initiation pipeline. On the left lives the existing pipeline consisting of PrimitiveWitness, TransactionKernel, SingleProof, etc. On the right lives a mirror image up to new names and up to one important new addition: the unconfirmed inputs that are being spent – for want of a better term, thruputs.
Thruputs are functionally equivalent to inputs in the sense of balancing a transaction. However, since they represent unconfirmed transactions, their RemovalRecords are not known yet. And so the type of this field is vector of AdditionRecords. When chainable transactions are chained, the operation goes through only if all thruputs of the successor are outputs of the predecessor, and the result eliminates those UTXOs altogether.
To accommodate this new field, a new kernel data structure is required: LinkKernel, which is the same as TransactionKernel except for the new field thruputs: Vec<AdditionRecord>. Likewise, the Transaction structure gets a chaining counterpart called LinkTx whose fields are kernel: LinkKernel and proof: Proof.
Whereas regular Transactions have three stages (PrimitiveWitness, ProofCollection, SingleProof), LinkTxs have only two: LinkWitness and LinkProof. The raise operation, which recursively validates all the proofs in a ProofCollection to produce a SingleProof, is exchanged for a direct validation of the witness. More precisely, the chaining-related operations are:
Forge : LinkWitness -> LinkTx(Comparable toProvefollowed byRaise.)Chain : LinkTx * LinkTx -> LinkTx(Comparable toMerge, except with cut-through.)Update : LinkTx -> LinkTx(Comparable toUpdate.)Fix : LinkTx -> Transaction(No comparison possible.) This operation works only ifthruputsis empty, and requires a new case in theSingleProofprogram.Cast : Transaction -> LinkTx(No comparison possible.) This operation populatesthruputswith the empty list.
Discussion
Merging the Prove-followed-by-Raise step into one Forge step is likely to be cheaper. There are still proofs to be recursively validated – namely, the type script and lock script proofs. However, these are the only proofs to be recursively validated in the Forge step whereas the high cost of the Raise step comes from also recursively validating other proofs, mainly RemovalRecordsIntegrity. The logic of RemovalrecordsIntegrity is still being validated; but its proof is not. Instead, its program code is duplicated and run in a different context.
At the same time, the raison d’ètre of the ProofCollection continues. This intermediate stage exists so that light machines can cheaply and securely initiate transactions, provided that they can find someone to do the heavy-lifting recursive part. Having a dual pipeline leaves the original one intact (up to the one extra branch into SingleProof).
Likewise, the dual pipeline does not affect the logic of the merge bit or of coinbase transactions. Composers can continue their current configuration.
One downside is that the SingleProof hash changes, along with complications downstream from there.