Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3f136bd

Browse files
authoredApr 2, 2025··
Support the withdraw zero trick (#1909)
Closes #1795 We can mock the ledger's certificate state with reward accounts for all withdrawals with 0 value in a transaction. This will allow included scripts with Rewarding script purpose to run once for the transaction. The idea of mocking the certificate state emerged in a discussion with @colll78. We can remove this once CIP-112 is implemented in the cardano-ledger. --- <!-- Consider each and tick it off one way or the other --> * [x] CHANGELOG updated * [x] Documentation updated -> new How-to * [x] Haddocks updated * [x] No new TODOs introduced
2 parents 2cb21c2 + 68d477d commit 3f136bd

File tree

20 files changed

+802
-313
lines changed

20 files changed

+802
-313
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ changes.
3737
- **BREAKING** Enable multi-party, networked "offline" heads by providing an `--offline-head-seed` option to `hydra-node`.
3838
- Drop `hydra-node offline` as a sub-command. Use `--offline-head-seed` and `--initial-utxo` options to switch to offline mode.
3939
40+
- Add support for "withdraw zero trick" transactions:
41+
- Any transaction with a `Rewarding` redeemer for a `Withdrawal` of `0 lovelace`, will be validated as if there would be a corresponding stake `RewardAccount` already registered.
42+
- No need to register the script's stake address before.
43+
4044
- Stream historical data from disk in the hydra-node API server.
4145
4246
- Record used and free memory when running `bench-e2e` benchmark.

‎docs/docs/configuration.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -200,25 +200,25 @@ As an offline head will not connect to any chain, we need to provide an `--offli
200200

201201
To initialize UTxO state available on the L2 ledger, offline mode takes an obligatory `--initial-utxo` parameter, which points to a JSON-encoded UTxO file. See the [API reference](https://hydra.family/head-protocol/api-reference/#schema-UTxO) for the schema.
202202

203-
Using this example UTxO:
203+
For example, the following UTxO contains 100 ADA owned by test key [alice-funds.sk](https://github.com/cardano-scaling/hydra/tree/master/hydra-cluster/config/credentials/alice-funds.sk):
204204
```json utxo.json
205205
{
206206
"0000000000000000000000000000000000000000000000000000000000000000#0": {
207-
"address": "addr_test1vqg9ywrpx6e50uam03nlu0ewunh3yrscxmjayurmkp52lfskgkq5k",
207+
"address": "addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z",
208208
"value": {
209209
"lovelace": 100000000
210210
}
211211
}
212212
}
213213
```
214214

215-
A (single participant) offline Hydra head can be started with:
215+
A single participant offline Hydra head can be started with our demo keys and protocol parameters:
216216
```shell
217217
hydra-node \
218218
--offline-head-seed 0001 \
219219
--initial-utxo utxo.json \
220-
--hydra-signing-key hydra.sk \
221-
--ledger-protocol-parameters protocol-parameters.json
220+
--hydra-signing-key demo/alice.sk \
221+
--ledger-protocol-parameters hydra-cluster/config/protocol-parameters.json
222222
```
223223

224224
As the node is not connected to a real network, genesis parameters that normally influence things like time-based transaction validation cannot be fetched and are set to defaults. To configure block times, set `--ledger-genesis` to a Shelley genesis file similar to the [shelley-genesis.json](https://book.world.dev.cardano.org/environments/mainnet/shelley-genesis.json).

‎docs/docs/how-to/etcd.md

-59
This file was deleted.

‎docs/docs/how-to/event-sinks-and-sources.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 5
2+
sidebar_position: 99
33
---
44

55
# Extend the node with event source and sinks

‎docs/docs/how-to/incremental-commit.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
---
2+
sidebar_position: 4
3+
---
4+
15
# Commit funds to an open Head
26

37
Assuming we already have an open Head and some funds on the L1 we would like to commit.

‎docs/docs/how-to/incremental-decommit.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 3
2+
sidebar_position: 5
33
---
44

55
# Decommit funds

‎docs/docs/how-to/operating-hydra.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 4
2+
sidebar_position: 6
33
---
44

55
# Operate a Hydra node

‎docs/docs/how-to/withdraw-zero.md

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Use withdraw zero trick
6+
7+
In this how-to we want to show how you can use the "wihtdraw zero trick" in Hydra. Until [CIP-112](https://cips.cardano.org/cip/CIP-0112) is implemented, there is only one way to run a script once per transaction - the so-called withdraw zero trick. Refer to the CIP or [this design pattern document](https://github.com/Anastasia-Labs/design-patterns/blob/main/stake-validator/STAKE-VALIDATOR.md) for more details about the general approach.
8+
9+
The Hydra L2 ledger is mostly _isomorphic_ to the L1 Cardano ledger. This means that it honors all the same ledger rules and transactions use the exact same format. However, some things related to the Cardano Proof-of-Stake consensus are represented different because they would not make much sense on an L2. For exapmle, transactions that [register stake certificates](https://docs.cardano.org/developer-resources/transaction-tutorials/stake-transaction) or [withdraw rewards](https://docs.cardano.org/developer-resources/transaction-tutorials/withdraw-transaction) don't work as-is on Hydra. However, for the purpose of "transaction-level validation", the withdraw zero trick can still be used as the Hydra L2 ledger **mocks script reward accounts** on the fly.
10+
11+
## Rewarding script
12+
13+
We are going to use the [dummyRewardingScript](https://github.com/cardano-scaling/hydra/blob/master/hydra-plutus/src/Hydra/Contract/Dummy.hs#L42-L53) as an example, which only validates the fact that we are in a `RewardingScript` context. Using GHCI we can determine the stake address and write the `rewarding.plutus` text envelope file for `cardano-cli`:
14+
15+
```haskell
16+
cabal repl hydra-plutus
17+
> import Prelude
18+
> import Hydra.Contract.Dummy
19+
> import Hydra.Cardano.Api
20+
> script = PlutusScript dummyRewardingScript
21+
> serialiseAddress $ makeStakeAddress (Testnet $ NetworkMagic 42) (StakeCredentialByScript $ hashScript script)
22+
"stake_test17rekjamvnjyn3c3tcjpxe7ea20g7aek9vdqkaa3jefknz3gc066pt"
23+
> writeFileTextEnvelope (File "../rewarding.plutus") Nothing script
24+
Right ()
25+
```
26+
27+
## Withdraw zero transaction
28+
29+
We can construct a transaction that withdraws zero lovelace from the rewarding script's "account". Usually, other purposes like spending a UTxO would require the reward script to run, but here we just assume there exists such a UTxO in the L2 state. You can initialize an [offline head](../configuration#offline-mode) for an easy demo set-up:
30+
31+
<details>
32+
<summary>Setup with offline head</summary>
33+
```
34+
cat > utxo.json <<EOF
35+
{
36+
"0000000000000000000000000000000000000000000000000000000000000000#0": {
37+
"address": "addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z",
38+
"value": {
39+
"lovelace": 100000000
40+
}
41+
}
42+
}
43+
EOF
44+
```
45+
46+
```shell
47+
cabal run hydra-node:exe:hydra-node -- \
48+
--offline-head-seed 0001 \
49+
--initial-utxo utxo.json \
50+
--ledger-protocol-parameters hydra-cluster/config/protocol-parameters.json \
51+
--hydra-signing-key demo/alice.sk
52+
```
53+
</details>
54+
55+
Then, build, sign and submit the transaction that runs the rewarding script:
56+
57+
```shell title="Withdraw zero transaction"
58+
cardano-cli latest transaction build-raw \
59+
--tx-in 0000000000000000000000000000000000000000000000000000000000000000#0 \
60+
--tx-in-collateral 0000000000000000000000000000000000000000000000000000000000000000#0 \
61+
--tx-out addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z+100000000 \
62+
--withdrawal stake_test17zadf9dcekqn9cxkg3q68y56f4d6ujxsv8l6kq4kz2cuduc3gm35e+0 \
63+
--withdrawal-script-file rewarding.plutus \
64+
--withdrawal-redeemer-value "{}" \
65+
--withdrawal-execution-units "(10000000000, 14000000)" \
66+
--protocol-params-file hydra-cluster/config/protocol-parameters.json \
67+
--fee 0 \
68+
--out-file tx.json
69+
70+
cardano-cli latest transaction sign \
71+
--tx-body-file tx.json \
72+
--signing-key-file hydra-cluster/config/credentials/alice-funds.sk \
73+
--out-file tx-signed.json
74+
75+
cat tx-signed.json | jq -c '{tag: "NewTx", transaction: .}' | websocat ws://localhost:4001
76+
```
77+
78+
## Registering script - not needed!
79+
80+
You might have noticed that registering the stake address was not needed - this is different than on the Cardano L1!
81+
82+
While you don't need to do it, you can still submit a transaction that registers the stake address and the L2 ledger will just ignore it:
83+
84+
```shell title="Register stake transaction"
85+
cardano-cli latest stake-address registration-certificate \
86+
--stake-address stake_test17zadf9dcekqn9cxkg3q68y56f4d6ujxsv8l6kq4kz2cuduc3gm35e \
87+
--key-reg-deposit-amt 2000000 \
88+
--out-file reg.cert
89+
90+
cardano-cli latest transaction build-raw \
91+
--tx-in 0000000000000000000000000000000000000000000000000000000000000000#0 \
92+
--tx-in-collateral 0000000000000000000000000000000000000000000000000000000000000000#0 \
93+
--tx-out addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z+98000000 \
94+
--certificate reg.cert \
95+
--certificate-script-file rewarding.plutus \
96+
--certificate-redeemer-value "{}" \
97+
--certificate-execution-units "(10000000000, 14000000)" \
98+
--protocol-params-file hydra-cluster/config/protocol-parameters.json \
99+
--fee 0 \
100+
--out-file tx.json
101+
102+
cardano-cli latest transaction sign \
103+
--tx-body-file tx.json \
104+
--signing-key-file hydra-cluster/config/credentials/alice-funds.sk \
105+
--out-file tx-signed.json
106+
107+
cat tx-signed.json | jq -c '{tag: "NewTx", transaction: .}' | websocat ws://localhost:4001
108+
```

‎hydra-cardano-api/src/Hydra/Cardano/Api.hs

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import Cardano.Api.Shelley as X (
9696
ShelleyGenesis (..),
9797
ShelleyLedgerEra,
9898
SigningKey (..),
99+
StakeCredential (..),
99100
VerificationKey (..),
100101
fromAlonzoCostModels,
101102
fromAlonzoPrices,

0 commit comments

Comments
 (0)
Please sign in to comment.