Skip to content

Commit ee34bad

Browse files
committed
Advanced scripting examples
1 parent 3a91c60 commit ee34bad

File tree

4 files changed

+826
-0
lines changed

4 files changed

+826
-0
lines changed

examples/ctv-congestion-control.minsc

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// CTV Congestion Control
3+
//
4+
// See https://utxos.org/uses/scaling/ for an overview. From the link:
5+
//
6+
// When there is a high demand for blockspace it becomes very expensive to make transactions.
7+
// By using OP_CTV, a large volume payment processor may aggregate all their payments
8+
// into a single O(1) transaction for purposes of confirmation. Then, some time later, the
9+
// payments can be expanded out of that UTXO when the demand for blockspace is decreased.
10+
11+
12+
//
13+
// Tree Setup & Funding
14+
//
15+
16+
// Split up the flat list of $payments into a CTV transaction tree
17+
fn ctvPayTree($payments, $tx_fee) {
18+
if len($payments) == 1 {
19+
// A final leaf node. Represents a payment output to a recipient's scriptPubKey
20+
[ "script": $payments.0.0, "amount": $payments.0.1 ]
21+
} else {
22+
// A branch node. Represents a CTV output that expands into a transaction with 2 outputs,
23+
// where each output can either be another CTV expansion branch or a final leaf payment.
24+
$left_size = len($payments) / 2;
25+
$right_size = len($payments) - $left_size;
26+
$left = ctvPayTree(slice($payments, 0, $left_size), $tx_fee);
27+
$right = ctvPayTree(slice($payments, $left_size, $right_size), $tx_fee);
28+
// Uses a radix of 2 to keep the example simpler, 4-5 for would be more optimal.
29+
30+
// ctv::hash() adds a default tx input (spending 000000...:0) when there are no inputs. The prevout txid:vout
31+
// doesn't affect the CTV hash, and will get updated later once the outpoint funding the tree root is known.
32+
$tx = tx [
33+
"outputs": [
34+
$left->script: $left->amount,
35+
$right->script: $right->amount,
36+
]
37+
];
38+
39+
[ "script": `ctv::hash($tx) OP_CTV`,
40+
"amount": $left->amount + $right->amount + $tx_fee, // fee added
41+
"tx": $tx, "left": $left, "right": $right ]
42+
}
43+
}
44+
45+
// Construct the tree
46+
$tree = ctvPayTree([
47+
tb1qrs85zz939tsf3nd3wsmjk3ye68zyfk4lxlkhmn: 0.1 BTC,
48+
tb1qv6rm00j52pvjqwv5dd2un9d6vfnqdp0m8fx8vx: 0.2 BTC,
49+
2MuSM1zTT3AUawArKp1MAc9KjpwTEsLZA75: 0.3 BTC,
50+
tb1qqlzzgn3x5xnuhmz7e7xen8l8sll9h9lnf43ekl: 0.4 BTC,
51+
2Mxo1dGnHBPZjR9Gone5fyxnMSn7qJcaizt: 0.5 BTC,
52+
], 200 sat);
53+
54+
// The total funding amount needed, including transaction fees for the entire tree
55+
$total_amount = $tree->amount;
56+
57+
// $tree->script can be paid to directly as a bare scriptPubKey, however wrapping in P2WSH makes
58+
// it easier to fund it from a wallet. The rest of the tree expansion will use bare scriptPubKeys.
59+
$tree_address = address(wsh($tree->script));
60+
61+
// The outpoint funding the tree's address/scriptPubKey, used as the initial input for expanding the tree.
62+
// Could specify just the txid:vout if bare scriptPubKey was used. For P2WSH, the witnessScript needs to be provided too.
63+
// $init_input = 0808943750af3eeac3cc0d5e74823a3f3bec154af98579fd1c1344b40181d00f:0;
64+
$init_input = [ "prevout": 0808943750af3eeac3cc0d5e74823a3f3bec154af98579fd1c1344b40181d00f:0, "witness": [ $tree->script ] ];
65+
66+
67+
//
68+
// Tree Expansion
69+
//
70+
71+
// With the $init_input funding outpoint known, we can finalize the tree with the actual outpoints descending from $init_input.
72+
// Returned as a flat list of nodes, each with the `path` leading to it, its final `outpoint` and the updated transaction.
73+
fn finalizeNodes($node, $outpoint, $path=[]) {
74+
$node = $node + [ "outpoint": $outpoint, "path": $path ];
75+
if $node->tx? {
76+
// Update the transaction input to spend the $outpoint, keeping other transactions fields as they were
77+
$tx = tx [ "input": $outpoint, "outputs": $node->tx->outputs, "version": $node->tx->version, "locktime": $node->tx->locktime ];
78+
$node = update($node, [ "tx": $tx ]);
79+
$left_flat = finalizeNodes($node->left, txid($tx):0, $path+[0]);
80+
$right_flat = finalizeNodes($node->right, txid($tx):1, $path+[1]);
81+
[ remove($node, [ "left", "right" ]) ] + $left_flat + $right_flat
82+
} else {
83+
[ $node ]
84+
}
85+
}
86+
87+
// Finalize the $tree to descent from the $init_input
88+
$tree_nodes = finalizeNodes($tree, $init_input);
89+
90+
// The full set of transactions needed to fully expand the tree
91+
$tree_txs_hex = filterMap($tree_nodes, |$n| if $n->tx? then hex($n->tx) else filterMap::skip);
92+
93+
//
94+
// Recipient's PoV
95+
//
96+
97+
// Get the chain of transactions leading up to the $destination:$amount payment
98+
fn findPayRoute($tree_nodes, [$destination, $amount]) {
99+
// Find the node paying $amount to the $destination
100+
$pay_node = find($tree_nodes, |$n| $n->script == $destination && $n->amount == $amount);
101+
$pay_node != null || throw("Payment not found");
102+
103+
// Find all nodes leading up to the payment, excluding the payment node itself
104+
$route = filter($tree_nodes, |$n| startsWith($pay_node->path, $n->path) && $n != $pay_node);
105+
106+
// Returns a tuple with the expansion transactions and the final outpoint funding $destination:$amount
107+
[ map($route, |$n| $n->tx), $pay_node->outpoint ]
108+
}
109+
110+
// The subset of transactions a recipient cares about to get their coins at $final_outpoint.
111+
// This can also serve as a proof of payment inclusion.
112+
[$pay_route_txs, $final_outpoint] = findPayRoute($tree_nodes, tb1qqlzzgn3x5xnuhmz7e7xen8l8sll9h9lnf43ekl:0.4 BTC);
113+
114+
// If $init_input was updated to an outpoint funding $tree_address with $tree->amount (1.500008 BTC),
115+
// it should be possible to broadcast these transactions in order:
116+
$pay_route_hex = map($pay_route_txs, hex);
117+
118+
// The last transaction in the chain includes the recipient's $final_outpoint
119+
assert::eq(txid(last($pay_route_txs)), $final_outpoint.0);
120+
assert::eq(last($pay_route_txs)->outputs.($final_outpoint.1)->script_pubkey, tb1qqlzzgn3x5xnuhmz7e7xen8l8sll9h9lnf43ekl->script_pubkey);
121+
assert::eq(last($pay_route_txs)->outputs.($final_outpoint.1)->amount, 0.4 BTC);
122+
123+
env::pretty()

0 commit comments

Comments
 (0)