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

ADR: Resource based API #1028

Merged
merged 6 commits into from
Sep 7, 2023
Merged

ADR: Resource based API #1028

merged 6 commits into from
Sep 7, 2023

Conversation

ch1bo
Copy link
Member

@ch1bo ch1bo commented Aug 18, 2023

An approach to compartmentalize the API into smaller parts to make them available for a wider range of use cases.

Inspired by previous experience on using HTTP methods for commands and HTTP upgraded connections for subscriptions.

Also very similar to GraphQL.


  • CHANGELOG updated or not needed
  • Documentation updated or not needed
  • Haddocks updated or not needed
  • No new TODOs introduced or explained herafter

@github-actions
Copy link

github-actions bot commented Aug 18, 2023

Transactions Costs

Sizes and execution budgets for Hydra protocol transactions. Note that unlisted parameters are currently using arbitrary values and results are not fully deterministic and comparable to previous runs.

Metadata
Generated at 2023-09-06 13:56:38.631794752 UTC
Max. memory units 14000000
Max. CPU units 10000000000
Max. tx size (kB) 16384

Script summary

Name Hash Size (Bytes)
νInitial 3ffaf6b87df35cb01a52eb23032b8f0b1a2a3ad3acf0930abc9c833a 4150
νCommit e4c32d6dc83b2917aa7805571f30437ad98b6d20d821d34d45943755 2093
νHead 8a43c1c4d5cb60c212e7aa540932f311cb914a1b6104f0f36a2aaaf0 8845
μHead 8712b8c5d1a13de4ebc88f6e5cfcfece88b98d23d81014cb1358d75c* 4055
  • The minting policy hash is only usable for comparison. As the script is parameterized, the actual script is unique per Head.

Cost of Init Transaction

Parties Tx size % max Mem % max CPU Min fee ₳
1 4646 11.17 4.43 0.48
2 4854 13.43 5.31 0.51
3 5058 15.44 6.08 0.54
5 5472 19.20 7.53 0.60
10 6493 29.33 11.44 0.76
45 13668 99.66 38.62 1.84

Cost of Commit Transaction

This is using ada-only outputs for better comparability.

UTxO Tx size % max Mem % max CPU Min fee ₳
1 599 12.67 4.97 0.31
2 787 16.41 6.66 0.37
3 972 20.28 8.39 0.42
5 1351 28.55 12.04 0.53
10 2284 51.44 21.99 0.82
18 3789 95.35 40.55 1.38

Cost of CollectCom Transaction

Parties UTxO (bytes) Tx size % max Mem % max CPU Min fee ₳
1 56 814 24.46 9.71 0.45
2 114 1143 37.51 15.03 0.61
3 169 1454 51.79 20.95 0.78
4 226 1774 69.81 28.37 1.00
5 284 2105 92.39 37.56 1.26

Cost of Close Transaction

Parties Tx size % max Mem % max CPU Min fee ₳
1 715 18.97 8.75 0.40
2 895 21.13 10.46 0.44
3 1105 22.72 11.97 0.47
5 1580 25.88 15.04 0.54
10 2603 34.19 22.57 0.70
50 10659 98.11 81.34 2.00

Cost of Contest Transaction

Parties Tx size % max Mem % max CPU Min fee ₳
1 677 22.65 9.95 0.43
2 945 24.80 11.85 0.48
3 1197 26.69 13.52 0.52
5 1556 30.59 16.62 0.59
10 2653 41.14 25.36 0.78
43 9133 98.41 75.04 1.89

Cost of Abort Transaction

Some variation because of random mixture of still initial and already committed outputs.

Parties Tx size % max Mem % max CPU Min fee ₳
1 4918 21.52 9.27 0.61
2 5358 36.66 15.95 0.80
3 5801 54.69 23.94 1.02
4 5766 63.23 27.25 1.11

Cost of FanOut Transaction

Involves spending head output and burning head tokens. Uses ada-only UTxO for better comparability.

Parties UTxO UTxO (bytes) Tx size % max Mem % max CPU Min fee ₳
5 0 0 4673 8.89 3.74 0.45
5 1 57 4708 10.10 4.50 0.47
5 5 283 4845 15.35 7.69 0.54
5 10 570 5032 21.91 11.68 0.63
5 20 1140 5392 35.03 19.67 0.81
5 30 1710 5753 48.16 27.67 0.99
5 40 2278 6112 61.30 35.66 1.17
5 50 2847 6471 74.70 43.77 1.35
5 69 3929 7153 99.68 58.99 1.70

End-To-End Benchmark Results

This page is intended to collect the latest end-to-end benchmarks results produced by Hydra's Continuous Integration system from the latest master code.

Please take those results with a grain of salt as they are currently produced from very limited cloud VMs and not controlled hardware. Instead of focusing on the absolute results, the emphasis should be on relative results, eg. how the timings for a scenario evolve as the code changes.

Generated at 2023-09-06 13:45:30.118655386 UTC

3-nodes Scenario

A rather typical setup, with 3 nodes forming a Hydra head.

Number of nodes 3
Number of txs 900
Avg. Confirmation Time (ms) 95.887074298
P99 253.09208813ms
P95 224.85509744999996ms
P50 70.3284285ms
Number of Invalid txs 0

Baseline Scenario

This scenario represents a minimal case and as such is a good baseline against which to assess the overhead introduced by more complex setups. There is a single hydra-node d with a single client submitting single input and single output transactions with a constant UTxO set of 1.

Number of nodes 1
Number of txs 300
Avg. Confirmation Time (ms) 3.789377563
P99 11.207213989999998ms
P95 7.893364000000002ms
P50 2.9226405ms
Number of Invalid txs 0

@github-actions
Copy link

github-actions bot commented Aug 18, 2023

Test Results

358 tests  +13   353 ✔️ +13   21m 47s ⏱️ -42s
121 suites +  4       5 💤 ±  0 
    6 files   +  1       0 ±  0 

Results for commit c1f9acb. ± Comparison against base commit bc0962d.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@pgrange pgrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all very clear proposal. I approve in draft status.

Please take a look at the questions in my comments.

docs/adr/2023-08-18_025-resource-based-api.md Outdated Show resolved Hide resolved
docs/adr/2023-08-18_025-resource-based-api.md Outdated Show resolved Hide resolved
| Path | GET | POST | PATCH | DELETE |
| :-------------------------- | :-------------------------- | :--------------------- | ------- | :----------------- |
| `/node/head/status` | `HeadStatus(..)` | - | - | - |
| `/node/head/utxo` | confirmed utxo | - | - | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing utxo is meant for utxo set here.

If I'm correct, what would mean confirmed utxo? Is it for the utxo that are included in a snapshot?

Also, I've noticed we currently have some issues with the clients only having access to the utxo included in a snapshot (see #612 ). I feel like /node/head/utxo, if containing the set of utxo included in a snapshot would exhibit the same problem and I would like us to solve it before. In my opinion, the client should, by default, only access the latest view of its node ledger when constructing a transaction. Whether or not these utxo have been included in a snapshot should not be considered at this step.

Last, but not least, we can currently have issues when the utxo set is very big and the snapshot confirmed message is heavy and slows down the node. That led some users to ask for utxo query primitives instead of getting the full utxo set. I'm mentioning this, although I'm not sure we want to address it right now as having a huge utxo set would make it impossible to close the head with the current implementation anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree: We should probably already provide sub-paths for individual UTxO and we already have had requests for more powerful UTxO query capabilities (eg. #797)

Copy link
Member Author

@ch1bo ch1bo Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points.

We usually mean any number of unspent transaction outputs when we just say utxo or UTxO. This is also how it is documented right now in the api reference and glossary IIRC.

I commented below that we likely need both, local and confirmed versions of utxo and transactions.

I will add a section on how the querying of such big resources could be improved using content types and query parameters (and maybe even pagination?). But maybe I also don't want to take a decision on that in this ADR (it's rather a consequence) and ultimately we might not be providing for this and suggest indexers instead?

| :-------------------------- | :-------------------------- | :--------------------- | ------- | :----------------- |
| `/node/head/status` | `HeadStatus(..)` | - | - | - |
| `/node/head/utxo` | confirmed utxo | - | - | - |
| `/node/head/transactions` | confirmed txs | `NewTx` + responses | - | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question: does confirmed txs mean they have been included in a snapshot?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is what I meant. I had Snapshot{confirmed} here before. Maybe that would be more clear?

| `/node/head/status` | `HeadStatus(..)` | - | - | - |
| `/node/head/utxo` | confirmed utxo | - | - | - |
| `/node/head/transactions` | confirmed txs | `NewTx` + responses | - | - |
| `/node/head/ledger` | `localUTxO` and `localTxs` | - | - | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I think I understand the following:

  • /node/head/utxo -> utxo set of the last known validated snapshot
  • /node/head/transactions -> transactions included in the last known validated snapshot (transitively)
  • /node/head/ledger -> utxo set and transactions of the local ledger, irrespective of the snapshots

I'm not sure we want to expose so much details to the customer. This notion of local transactions and local utxo set feels very internal to the Hydra protocol specification/implementation.

I think, as a client of Hydra, that I want to be able to :

  1. init/open/close/contest/fanout a head
  2. build new L2 transactions
  3. know about the finality of a transactions on L2

For 1. I need the most recent view of the ledger from my node. Otherwise, I could build locally invalid transactions (see #612 ) so /node/head/ledger would be enough, although I would name it differently.

For 2. I need the information that a given transaction has been included in a signed snapshot and, for that, I guess I would want to have an entry point to get the status of the transaction itself and it would inform me about the fact that it's pending and snapshotted.

That would lead to the following entry points:

  • /node/head/utxo -> utxo set known by my local node
  • /node/head/transactions -> transactions known by my local node
    • /node/head/transactions/<id> -> informations about transaction of id id, can be used to check for L2 finality of a transaction
  • /node/head/ledger -> removed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say exactly the other way around?

For 1) you would care about which UTxO are available on the L2 after opening and which would be available if we close (any non confirmed utxo would not be included!)

For 2) you would need the most recent local view of the UTxO set, such that your node accepts transactions moving this forward.

For 3) we can do this individual query as you propose, but I would advise against it as it does not allow us to setup a subscription for all confirmed L2 transactions, which is a use case we know exists.As you might

So we are in the situation where likely need both, the local view and the confirmed view. I will try to make that separation more clear in the example resources and also should make it clear that the ADR is not primarily about specifying the full API, but more to exercise the concept of mapping endpoints to existing semantics and see whether we could address the requirements from the context.

binary (`cbor`) output of `transaction` fields in several of these using a
query parameter.

- Many of these features have been added in a "quick and dirty" way, by monkey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this adr assume also changing the "quick and dirty" way of doing things?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I added some notes on how content types and query parameters can lead to a fine grained control and more standard way of configuring responses.

docs/adr/2023-08-18_025-resource-based-api.md Outdated Show resolved Hide resolved
into topics on the API layer.

- Each resource has a _latest_ state, which can be queried or clients may
subscribe to any model changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would separate that in 2 statements to make it clearer there are 2 different things, eg. :

  • Clients can synchronously query the latest state of each resource
  • Clients can subscribe to be notified of state changes for each resource

Note this definitely looks very similar to what GraphQL provides :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are subscriptions in scope of standard graphql? Couldn't see it in most implementations and only apollo supports that.

It's quite inspired by graphql and maybe just doing that is simpler? Happy to discuss!

subscribe to any model changes.

- A resource's `model` type needs to be a result of a pure `projection` from
server output events, i.e. `project :: model -> ServerOutput -> model`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not clear to me: How can a type be the result of projection? Do you mean that each resource's value is the result of accumulating (folding? reducing?) some sequence of ServerOutputs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Updated the wording on this one. Hope it's more clear. Not sure though if projection is the right word here. I also like "reduce" as it may be familiar with most people from the frontend world of redux state management systems.

docs/adr/2023-08-18_025-resource-based-api.md Outdated Show resolved Hide resolved

| Path | GET | POST | PATCH | DELETE |
| :-------------------------- | :-------------------------- | :--------------------- | ------- | :----------------- |
| `/node/head/status` | `HeadStatus(..)` | - | - | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have /node as the first item in the path?

Copy link
Member Author

@ch1bo ch1bo Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. The idea here really was to make sub-paths always included in the parent. /node would contain all the remaining data available at /node/* endpoints. There is also the intention to keep / with the same semantics as before.

Shall we

  1. Make it an exception to the rule and say / will contain not everything in /*, but the original behavior of accepting a websocket upgrade with the same messages as before? or
  2. Rename /node to /v1 and make /v1/* include all the data from sub-paths to make it more consistent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted for doing 2) and would love your feedback again.

| Path | GET | POST | PATCH | DELETE |
| :-------------------------- | :-------------------------- | :--------------------- | ------- | :----------------- |
| `/node/head/status` | `HeadStatus(..)` | - | - | - |
| `/node/head/utxo` | confirmed utxo | - | - | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree: We should probably already provide sub-paths for individual UTxO and we already have had requests for more powerful UTxO query capabilities (eg. #797)

docs/adr/2023-08-18_025-resource-based-api.md Outdated Show resolved Hide resolved
@ch1bo ch1bo self-assigned this Aug 30, 2023
@ch1bo ch1bo removed their assignment Aug 31, 2023
Copy link
Contributor

@abailly-iohk abailly-iohk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it, thanks for considering my suggestions and incorporating them in such a thorough way. I have only added minor comments which can definitely be ignored.

to know the latest UTxO set for constructing transactions.

- Inclusion of the whole UTxO set in the head is not always desirable and
filtering by address would be beneficial. (not addressed in this ADR though)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just mention adding querying capabilities in general and point to the relevant discussion (#797)?

filtering by address would be beneficial. (not addressed in this ADR though)

- As [ADR-15](/adr/15) also proposes, some clients may not need (or should
not have) access to administrative information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention this won't be addressed in this ADR either? Or is it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indirectly. There is a consequence (number 4) which mentions this again.

not have) access to administrative information.

- It is often a good idea to separate the responsibilities of Commands and
Queries (CQRS), as well as the model they use.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## Decision

- Drop `GetUTxO` and `GetUTxOResponse` messages as they advocate a
request/response way of querying.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

connection, push the _latest_ state as first message and any resource
state updates after.

- Other HTTP verbs may be accepted by a resource handler, i.e. to issue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HEAD or OPTIONS are not really about commands and the former might be useful in general to eg. retrieve the size of a response.

via the corresponding websocket connection.

- `Accept` request headers can be used to configure the `Content-Type` of the
response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

| `/v1/` | all `/v1/*` data | - | - | - |

Multiple heads are out of scope now and hence paths are not including a
`<headId>` variable section.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is an ADR, and not an implementation plan, I would like to suggest we include the headId in the path to queries retrieving the state of a particular head. I think multiple heads are not that far down the road, this does not add much complexity, and seems to be more consistent with traditional practices.
I am fine with leaving it as it is if you think it's not worth the hassle.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep it out for now.


- Versioned API allows clients to detect incompatibility easily.

- Need to rewrite how the `hydra-tui` is implemented.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or implement a hydra-web-ui ;) ?

This should make it more clear that content-types and query parameters
should be used to negotiate response structure on a per-resource basis
The included example resources should only be seen as that. Not all
details about separating clearly between local and confirmed utxo/txs
are resolved in this ADR.
@ch1bo ch1bo merged commit abe5d51 into master Sep 7, 2023
37 checks passed
@ch1bo ch1bo deleted the adr-resource-based-api branch September 7, 2023 09:15
@ch1bo ch1bo mentioned this pull request Sep 26, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants