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

Explain configuration errors #1184

Merged
merged 7 commits into from
Dec 1, 2023

Conversation

abailly-iohk
Copy link
Contributor

This PR is a follow-up to a discussion we've had in #1179 about user-friendlier error handling in the hydra-node executable. The idea is simply to use Haskell's exception mechanism to signal various kinds of misconfigurations as structured data and separate explain function to provide human-readable error messages. The expected benefit is that this decouples the errors' definition from their representation, making it possible to provide different representations depending on the context, eg. i18n perhaps, or JSON.

This PR only unifies errors from the RunOptions parsing and the protocol parameters parsing, it's missing the errors stemming from misalignment between given configuration and persisted state, and is a way to foster discussion about our error handling strategy.


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

@abailly-iohk abailly-iohk requested a review from a team November 29, 2023 07:37
Copy link

github-actions bot commented Nov 29, 2023

Test Results

373 tests  +1   368 ✔️ +1   20m 26s ⏱️ - 1m 40s
127 suites +1       5 💤 ±0 
    5 files   ±0       0 ±0 

Results for commit ed04d04. ± Comparison against base commit c1185cf.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Nov 29, 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-12-01 13:30:53.568956069 UTC
Max. memory units 14000000
Max. CPU units 10000000000
Max. tx size (kB) 16384

Script summary

Name Hash Size (Bytes)
νInitial 4868d5365af5120ae0b3c93b819d3452a3cbdcc98595da2a7ae765b5 4069
νCommit 171a1e6bdbc8aa96d957a65b3f505517386af06ba265e3f784741f67 2050
νHead e89b0c4a6155bac2434d1e500bd49c155b2b56744ccf5a0efa72a82e 9092
μHead 6849328242b5912ad218f134378e6baff11f3e74f7e36dcf8e13d53e* 4062
  • 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 4585 10.87 4.31 0.47
2 4784 13.23 5.22 0.51
3 4985 15.42 6.05 0.54
5 5388 19.67 7.67 0.60
10 6396 30.71 11.88 0.77
41 12628 99.44 38.11 1.78

Cost of Commit Transaction

This is using ada-only outputs for better comparability.

UTxO Tx size % max Mem % max CPU Min fee ₳
1 534 12.22 4.81 0.31
2 720 15.93 6.48 0.36
3 912 19.77 8.20 0.41
5 1285 27.87 11.80 0.52
10 2219 50.58 21.69 0.81
18 3713 94.20 40.16 1.36

Cost of CollectCom Transaction

Parties UTxO (bytes) Tx size % max Mem % max CPU Min fee ₳
1 57 480 22.24 8.89 0.42
2 114 590 35.73 14.35 0.57
3 170 700 47.36 19.27 0.70
4 226 814 61.27 25.12 0.86
5 280 920 79.43 32.66 1.07

Cost of Close Transaction

Parties Tx size % max Mem % max CPU Min fee ₳
1 543 17.76 8.34 0.38
2 755 19.47 9.98 0.41
3 895 20.70 11.24 0.44
5 1309 24.82 14.90 0.52
10 2215 33.17 22.80 0.68
50 9026 95.71 83.31 1.92

Cost of Contest Transaction

Parties Tx size % max Mem % max CPU Min fee ₳
1 566 21.33 9.70 0.42
2 754 23.19 11.36 0.45
3 884 24.95 12.88 0.48
5 1331 29.09 16.52 0.56
10 2173 37.79 24.42 0.73
45 8078 99.35 79.70 1.88

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 4545 20.18 8.79 0.58
2 4719 33.53 14.79 0.74
3 4850 50.63 22.42 0.93
4 4856 57.10 24.92 1.00
5 5229 92.25 41.09 1.42

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 4417 8.98 3.78 0.44
5 1 57 4453 10.28 4.57 0.46
5 5 283 4594 15.70 7.84 0.54
5 10 571 4778 21.95 11.72 0.62
5 20 1137 5133 35.02 19.70 0.80
5 30 1706 5494 48.13 27.70 0.98
5 40 2274 5852 61.12 35.65 1.16
5 50 2846 6215 73.88 43.51 1.33
5 69 3923 6893 98.57 58.63 1.67

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-12-01 13:24:08.912387675 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) 22.589443328
P99 46.77559071999982ms
P95 32.6013344ms
P50 20.5578745ms
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) 4.399864263
P99 8.80719950999999ms
P95 5.687454250000002ms
P50 4.16141ms
Number of Invalid txs 0

@ch1bo ch1bo self-assigned this Nov 29, 2023
Copy link
Collaborator

@ch1bo ch1bo left a comment

Choose a reason for hiding this comment

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

I think this is a good small step and I approve merging this right away!

Thank you for putting the run refactor into a dedicated commit, that made reviewing possible.

I like the fact that we have a function mapping one or the other error to some english text (and not re-use Show)

I do not like that this would force us to make all exceptions that are explained a ConfigurationException (or a sub-type of it).

I think a good next step, would be to (following the example in exceptions):

  • create a type class Explainable which has explain as the single method
  • create a type and functions:
data SomeExplainableException = forall e . (Explainable e, Exception e) => SomeExplainableException e

instance Show SomeExplainableException where
    show (SomeExplainableException e) = show e
    
instance Exception SomeExplainableException

explainableExceptionToException :: (Explainable e, Exception e) => e -> SomeException
explainableExceptionToException = toException . SomeExplainableException

explainableExceptionFromException :: (Explainable e, Exception e) => SomeException -> Maybe e
explainableExceptionFromException x = do
    SomeExplainableException a <- fromException x
    cast a
  • define any exception that should be explainable by this top-level catch using these, e.g. the ConnectException from Hydra.Chain.Direct
data ConnectException = ConnectException
  { ioException :: IOException
  , nodeSocket :: SocketPath
  , networkId :: NetworkId
  }
  deriving stock (Show)

instance Exception ConnectException where
  toException = explainableExceptionToException
  fromException = explainableExceptionFromException
  
instance Explainable ConnectException where
  explain _ = "Use your imagination"

Alternative: Just define displayException for all our exceptions and not catch at all.

@ch1bo ch1bo removed their assignment Nov 29, 2023
@abailly-iohk
Copy link
Contributor Author

abailly-iohk commented Nov 29, 2023

@ch1bo Thanks a lot for the review. I think what you propose is sensible and I would like to turn that into a proper ADR. So the toplevel catch would look like:

   run `catch` \ (SomeDisplayableException e) -> die $ display e

? I did not know how the Exception typeclass worked, thanks for the explanation.

@abailly-iohk
Copy link
Contributor Author

After all, we said we would stick to boring Haskell :-D

@abailly-iohk
Copy link
Contributor Author

Thinking again about your proposal, I see one issue: With a "naive" Explainable type-class there would only ever be one possible way to explain an exception which is precisely what I would like to avoid. And therefore there's no essential difference between this approach and using displayException: The message would be fixed at the definition site of the exception and independent from the context in which it is displayed, at the usage site.

While I agree we don't want to force any coupling between the various exceptions we could throw, I still think it's both simpler and more malleable to let the usage context decide what to do and how to display the exceptions, which implies some form of grouping of exceptions at the toplevel, just like we do for the logging. I don't see this as a problem as long as it's contained within the application part of the node, by which I mean the part which ties together the various components into a coherent executable.

Copy link
Contributor

@v0d1ch v0d1ch left a comment

Choose a reason for hiding this comment

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

nice!

@ch1bo
Copy link
Collaborator

ch1bo commented Dec 1, 2023

Thinking again about your proposal, I see one issue: With a "naive" Explainable type-class there would only ever be one possible way to explain an exception which is precisely what I would like to avoid. And therefore there's no essential difference between this approach and using displayException: The message would be fixed at the definition site of the exception and independent from the context in which it is displayed, at the usage site.

While I agree we don't want to force any coupling between the various exceptions we could throw, I still think it's both simpler and more malleable to let the usage context decide what to do and how to display the exceptions, which implies some form of grouping of exceptions at the toplevel, just like we do for the logging. I don't see this as a problem as long as it's contained within the application part of the node, by which I mean the part which ties together the various components into a coherent executable.

While the Explainable type class may be about the explain :: a -> Text into english text conversion, we could very well require ToJSON or ExplainInternationalized type classes (and/or expand the type class we will have at that point).

I do not understand why you don't like implementations of these explanations to be done at the definition site, after all the part of the application which defines it must know "what this means" and maybe "how it can be avoided".

Having a central place where all this knowledge needs to be replicated to again is suboptimal in my opinion.

But we have both options with type classes (instances are non-orphans when defined in the class module or the type module).

@abailly-iohk
Copy link
Contributor Author

I do not understand why you don't like implementations of these explanations to be done at the definition site, after all the part of the application which defines it must know "what this means" and maybe "how it can be avoided".

That's The part of the application that defines the error does not have the context in which this error will be reported, there is a difference between knowing something is wrong and knowing how to repair or avoid once you know it's wrong. For example, when you detect a connection is wrong the message might be different in the context of a main program where you define a parameter for the connection, an end-user web page where this connection is implicitly defined by your location, or from a field, etc.

The part of the code throwing the error does not have in general any knowledge about how this error came to happen, which the use-site might have.

In other words, the "meaning" of the error is (in part) context dependent, at least if you want to provide accurate and helpful error messages to users and systems.

@abailly-iohk abailly-iohk force-pushed the abailly-iohk/explain-configuration-errors branch from d6a0fda to 5d085c3 Compare December 1, 2023 10:38
@abailly-iohk abailly-iohk force-pushed the abailly-iohk/explain-configuration-errors branch from 5d085c3 to ed04d04 Compare December 1, 2023 13:17
@abailly-iohk abailly-iohk merged commit a8ce92d into master Dec 1, 2023
20 checks passed
@abailly-iohk abailly-iohk deleted the abailly-iohk/explain-configuration-errors branch December 1, 2023 13:46
@ch1bo ch1bo added this to the 0.15.0 milestone Dec 22, 2023
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

3 participants