Skip to content

Commit

Permalink
Cleanup incremental decommit - Part 2 (#1513)
Browse files Browse the repository at this point in the history
Second part of promised cleanup on #1344. This holds not as crucial
changes as the other and more refactorings than fixes.

* This should resolve most of the comments I had about #1344 and this
also does the "clearing" of $\textcolor{red}{\\red}$ areas in the
specfication.

* Validate a client decommit request against the `localUTxO`.

We can (and should) check against the local ledger state in both cases
as it's our local
  node's view of what is valid and what it would snapshot next anyways.

* Make AckSn processing emit a PartySignedSnapshot

  This matches with the Σ̂[j] ← σj assignment from the pseudo-code.
  This does not change the semantics, as the SnapshotConfirmed state
  changed event includes the full multi-signature anyways.

* Only request snapshot on ReqDec if no snapshot in flight

* Added a missing $\setminus outputs(tx_\omega)$ to the spec.

* Resolved "Spec Gap" comments in `HeadLogic`

* Refactored `CloseTx`, `ContestTx` and `DecrementTx` to take
`ConfirmedSnapshot`s and remove redundant `closeUTxOToDecommit` from
`CloseTx`.

* Simplify/shorten/reword incremental decommit how-to

* Removed various unused things

* Enhance / properly use `TxTraceSpec` to detect problems in contest
transaction creation by modeling versions in `ModelSnapshot`.

---

* [x] CHANGELOG updated
* [x] Documentation updated
* [x] Haddocks updated
* [ ] No new TODOs introduced or explained herafter
  • Loading branch information
ch1bo authored Jul 17, 2024
2 parents aa928f5 + df2fcf2 commit 400c19b
Show file tree
Hide file tree
Showing 44 changed files with 37,170 additions and 27,716 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ changes.

## [0.18.0] - Unreleased

- Add capability to decommit or take some funds out of a running Head.

- **DO NOT RELEASE** as the tested `cardano-node` version is not intended to be used on `mainnet` yet.

- Tested with `cardano-node 8.11.0` and `cardano-cli 8.23.1.0`.

- **BREAKING** Changes to the `hydra-node` API `/commit` endpoint [#1463](https://github.com/input-output-hk/hydra/pull/1463):
- Removed the check that prevented committing UTxOs from an internal `hydra-node` wallet.
- `SpendingNodeUtxoForbidden` error was removed.

- Add capability to move UTxO out of an open Head to the Cardano main chain:
- Submitting a decommit transaction to `POST /decommit` or as `Decommit` command through websocket, requests removal of this transactions' outputs from the head.
- When successful, `DecommitApproved` and `DecommitFinalized` indicate that all outputs are made available on the layer one.
- Invalid transactions are explained through a `DecommitInvalid` server output.

- Change `--start-chain-from` to always use the newer point when also a head state is known.

Expand Down
108 changes: 45 additions & 63 deletions docs/docs/how-to/incremental-decommit.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,27 @@ sidebar_position: 3
---

# Decommit funds
> This example assumes you have the hydra-node repo at your disposal together with `hydra-node`, `hydra-tui`, `cardano-cli` and `curl` binaries and that cardano-node is running on local devnet.

To take out some `UTxO` present in an already open Head and send it back to the layer one, you need to use the `Decommit` command of the WebSocket API.
To take out some `UTxO` present in an open head and send it back to the layer one, you need to do a so-called `decommit`.

To do this we need to find out which `UTxO` we can spend.
This how-to assumes that we are in a similar situation as in the [Getting Started](../getting-started) or [Testnet tutorial](../tutorial). Depending on who owns something in your head, you might need to update the instructions of this tutorial. In our example we decommit funds owned by Alice from their address:

In this example we will use Alice and her external wallet key so first let's find out the address:
```shell
cardano-cli address build \
--payment-verification-key-file hydra-cluster/config/credentials/alice-funds.vk \
--testnet-magic 42 \
--out-file alice-funds.addr
export WALLET_SK=credentials/alice-funds.sk
export WALLET_ADDR=addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z
```

output:
```shell
addr_test1vp5cxztpc6hep9ds7fjgmle3l225tk8ske3rmwr9adu0m6qchmx5z
```

Next, let's query the state of Alice's UTxO available in the head:
First, we need to find out which `UTxO` we can spend using our address:

```shell
curl localhost:4001/snapshot/utxo \
| jq "to_entries \
| map(select(.value.address == \"$(cat alice-funds.addr)\")) \
| from_entries" \
| jq "with_entries(select(.value.address == \"${WALLET_ADDR}\"))" \
> utxo.json
```

output:
<details>
<summary> Example output</summary>

```json
{
"f6b004be1cf95dbd3d0abc3daceac40ef6401e502972a919e5e52564b9f5740b#0": {
Expand All @@ -58,76 +49,67 @@ output:
}
```

We can also do this by querying the state of Alice's funds in the `websocat` session:

```shell
websocat -U "ws://0.0.0.0:4001?history=no" \
| jq "select(.tag == \"Greetings\") \
| .snapshotUtxo \
| with_entries(select(.value.address == \"$(cat alice-funds.addr)\"))" \
> utxo.json
```

Now we need another key to send funds to and construct a decommit
transaction which will be sent to the `hydra-node` to initiate the process of
sending funds back to the layer one.

Let's create new wallet key pair and address:
</details>

```shell
cardano-cli address key-gen \
--verification-key-file credentials/wallet.vk \
--signing-key-file credentials/wallet.sk

cardano-cli address build \
--verification-key-file credentials/wallet.vk \
--testnet-magic 42 \
--out-file credentials/wallet.addr
```
Now, the `decommit` command requires us to build a transaction that proves we can spend what we want to decommit. The outputs of this transaction will be the outputs that are also going to be made available on the main chain.

Let's pick the first `UTxO` which has total of 5 ADA available and use it to decommit.
> We rely on `hydra-node` to balance the transaction.
Using the `UTxO` we queried before, we need to construct and sign a decommit transaction.
For example, to spend the first UTxO queried above in a transaction sending the same value to Alice's key (so she can spend it on the layer one later):

```shell
LOVELACE=$(jq -r 'to_entries[0].value.value.lovelace' < utxo.json)
cardano-cli transaction build-raw \
--tx-in $(jq -r 'to_entries[0].key' < utxo.json) \
--tx-out $(cat credentials/wallet.addr)+${LOVELACE} \
--tx-out ${WALLET_ADDR}+${LOVELACE} \
--fee 0 \
--out-file decommit.json
```

You can inspect the transaction with

```shell
cardano-cli transaction view --tx-file decommit.json
```

As the transaction spends from Alices funds in the Hydra head, we also need to
sign it with her key:

```shell
cardano-cli transaction sign \
--tx-file decommit.json \
--signing-key-file hydra-cluster/config/credentials/alice-funds.sk \
--signing-key-file ${WALLET_SK} \
--out-file alice-decommit-tx-signed.json
```

With the signed decommit transaction, now we can submit it to the `hydra-node`:
With the signed decommit transaction, now we can submit it to the `/decommit` endpoint:

```shell
curl -X POST 127.0.0.1:4001/decommit \
--data @alice-decommit-tx-signed.json
```

> We can also do this by sending the `Decommit` client input to the `websocat` session:
> ```shell
> cat alice-decommit-tx-signed.json \
> | jq -c '{tag: "Decommit", decommitTx: .}' \
> | websocat "ws://127.0.0.1:4001?history=no"
<details>
<summary>Alternative using websocket</summary>

We can also submit a `Decommit` client input using a websocket:
```shell
cat alice-decommit-tx-signed.json \
| jq -c '{tag: "Decommit", decommitTx: .}' \
| websocat "ws://127.0.0.1:4001?history=no"
```

If you haven't already open a websocat session (`websocat ws://0.0.0.0:4001`).
There you will see a `DecommitRequested` message which indicates a decommit is requested.
After some time a `DecommitFinalized` can be observed which concludes the decommit process and after which the funds are available on the layer one.
</details>

If you haven't already, open a websocket session using `websocat ws://0.0.0.0:4001` now.

In the message history you will see a `DecommitRequested` message which
indicates a decommit is requested.

After some time, a `DecommitFinalized` can be observed which concludes the decommit process and after which the funds are available on the layer one.

To confirm, you can query the funds of the wallet on the layer one:
To confirm, you can query the funds of the wallet on the layer one from a `cardano-node`:

```shell
cardano-cli query utxo \
--socket-path node.socket \
--address $(cat credentials/wallet.addr) \
--testnet-magic 42 \
--output-json \
| jq
--address ${WALLET_ADDR} \
--output-json | jq
```
38 changes: 20 additions & 18 deletions hydra-cluster/test/Test/DirectChainSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ spec = around (showLogsOnFailure "DirectChainSpec") $ do
, version = v
}

postTx $ CloseTx headId headParameters (ConfirmedSnapshot{snapshot, signatures = aggregate [sign aliceSk snapshot]}) v toDecommit
postTx $ CloseTx headId headParameters v (ConfirmedSnapshot{snapshot, signatures = aggregate [sign aliceSk snapshot]})

deadline <-
waitMatch aliceChain $ \case
Expand Down Expand Up @@ -442,7 +442,7 @@ spec = around (showLogsOnFailure "DirectChainSpec") $ do
aliceChain `observesInTime` OnCollectComTx headId

-- Alice close with the initial snapshot U0
postTx $ CloseTx headId headParameters InitialSnapshot{headId, initialUTxO = someUTxO} 0 mempty
postTx $ CloseTx headId headParameters 0 InitialSnapshot{headId, initialUTxO = someUTxO}
deadline <- waitMatch aliceChain $ \case
Observation{observedTx = OnCloseTx{snapshotNumber, contestationDeadline}}
| snapshotNumber == 0 -> Just contestationDeadline
Expand All @@ -460,14 +460,15 @@ spec = around (showLogsOnFailure "DirectChainSpec") $ do
}
postTx $
ContestTx
headId
headParameters
( ConfirmedSnapshot
{ snapshot = snapshot1
, signatures = aggregate [sign aliceSk snapshot1]
}
)
0
{ headId
, headParameters
, openVersion = 0
, contestingSnapshot =
ConfirmedSnapshot
{ snapshot = snapshot1
, signatures = aggregate [sign aliceSk snapshot1]
}
}
aliceChain `observesInTime` OnContestTx{headId, snapshotNumber = 1, contestationDeadline = deadline}

-- Alice contests with some snapshot U2 -> expect fail
Expand All @@ -483,14 +484,15 @@ spec = around (showLogsOnFailure "DirectChainSpec") $ do
let contestAgain =
postTx $
ContestTx
headId
headParameters
( ConfirmedSnapshot
{ snapshot = snapshot2
, signatures = aggregate [sign aliceSk snapshot2]
}
)
1
{ headId
, headParameters
, openVersion = 1
, contestingSnapshot =
ConfirmedSnapshot
{ snapshot = snapshot2
, signatures = aggregate [sign aliceSk snapshot2]
}
}
-- NOTE: We deliberately expect the transaction creation and
-- submission code of the Chain.Direct module to fail here because
-- the scripts don't validate. That is, the on-chain code prevented
Expand Down
4 changes: 2 additions & 2 deletions hydra-node/bench/tx-cost/TxCost.hs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ computeContestCost = do
pointInTime <- genPointInTimeBefore (getContestationDeadline stClosed)
let cp = ctxContestationPeriod ctx
let contestUtxo = getKnownUTxO stClosed <> getKnownUTxO cctx
pure (unsafeContest cctx contestUtxo headId cp snapshot pointInTime 0, contestUtxo)
pure (unsafeContest cctx contestUtxo headId cp 0 snapshot pointInTime, contestUtxo)

computeAbortCost :: IO [(NumParties, TxSize, MemUnit, CpuUnit, Coin)]
computeAbortCost =
Expand Down Expand Up @@ -249,7 +249,7 @@ computeFanOutCost = do
cctx <- pickChainContext ctx
let cp = ctxContestationPeriod ctx
(startSlot, closePoint) <- genValidityBoundsFromContestationPeriod cp
let closeTx = unsafeClose cctx (getKnownUTxO stOpen) headId (ctxHeadParameters ctx) snapshot startSlot closePoint 0
let closeTx = unsafeClose cctx (getKnownUTxO stOpen) headId (ctxHeadParameters ctx) 0 snapshot startSlot closePoint
stClosed = snd . fromJust $ observeClose stOpen closeTx
deadlineSlotNo = slotNoFromUTCTime systemStart slotLength (getContestationDeadline stClosed)
utxoToFanout = getKnownUTxO stClosed <> getKnownUTxO cctx
Expand Down
3 changes: 0 additions & 3 deletions hydra-node/golden/Message SimpleTx.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@
}
},
{
"decommitRequester": {
"vkey": "31eb008ec9b806ac368ea4a20f7299dc9f8666812ceecdaf8536ab0caeddc05e"
},
"tag": "ReqDec",
"transaction": {
"id": 23,
Expand Down
Loading

0 comments on commit 400c19b

Please sign in to comment.