Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup incremental decommit - Part 2 #1513

Merged
merged 25 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
09e94c1
Document and re-order arguments of 'decrement'
ch1bo Jul 15, 2024
1adde59
Remove unused Semigroup and Monoid of UTxOHash
ch1bo Jul 15, 2024
2ea0947
Simplify decommit how-to
ch1bo Jul 16, 2024
edd7e1e
Expand changelog entry
ch1bo Jul 16, 2024
c626d0a
Remove decommitRequester
ch1bo Jul 16, 2024
79fccef
Update haddocks and drop a redundant test
ch1bo Jul 16, 2024
af05022
Check decommit request against local ledger
ch1bo Jul 16, 2024
edd175a
Only request snapshot on ReqDec if no snapshot in flight
ch1bo Jul 16, 2024
c220a1e
Resolve some spec gap comments in head logic
ch1bo Jul 16, 2024
8710b17
Make AckSn processing emit a PartySignedSnapshot
ch1bo Jul 16, 2024
e581663
Align HeadLogic closer with spec
ch1bo Jul 16, 2024
e9bc966
Combine requires when processing a ReqSn
ch1bo Jul 16, 2024
d0dd5ef
Add DecommitRecorded and DecommitFinalized to genStateChanged
ch1bo Jul 16, 2024
d0e6c8a
Remove unused SnapshotOutcome data types (and module)
ch1bo Jul 16, 2024
558c4fb
Consolidate Wait outcomes for decommit request processing
ch1bo Jul 16, 2024
1c84c02
Not assert starting state in HeadLogicSpec
ch1bo Jul 16, 2024
eea4712
Remove redundant language pragmas
ch1bo Jul 16, 2024
399e008
Refactor CloseTx/close/closeTx and add docs to openVersion
ch1bo Jul 16, 2024
c6aaf86
Also refactore ContestTx/contest/contestTx to match close
ch1bo Jul 16, 2024
63d24ba
Also refactor DecrementTx/decrement/decrementTx to take a ConfirmedSn…
ch1bo Jul 16, 2024
51852cf
Untangle some healthyXXX functions
ch1bo Jul 16, 2024
deb4923
Add AssertionFailed logic error outcome and use it onDecrementTx
ch1bo Jul 17, 2024
3126a7d
Review and unmark red on-chain spec areas
ch1bo Jul 17, 2024
52ebe05
Clear gaps and red markup in offchain
ch1bo Jul 17, 2024
df2fcf2
Fix TxTraceSpec by modelling snapshot versions
ch1bo Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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