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

Add new interim governance commands: {create, answer, verify}-poll #5112

Merged
merged 10 commits into from
Apr 17, 2023

Conversation

Jimbo4350
Copy link
Contributor

@Jimbo4350 Jimbo4350 commented Apr 17, 2023

Original PR by @KtorZ: 5050

The Cardano Foundation proposes a mechanism for polling Cardano stake pool operators on specific topics. Polls are done on-chain through transaction metadata and authenticated through stake pool credentials (either VRF public key similar to what's described in CIP-0022 or Ed25519 cold key).

The goal is to gather opinions on governance matters such as protocol parameters updates. This standard is meant to be an inclusive interim solution while the work on a larger governance framework such as CIP-1694 continues.

See proposed CIP for details.


This commits adds three new commands:

  • create-poll: For the current governing entities, as a means to create new polls.

  • answer-poll: For participants who want to answer a given poll.

  • verify-poll: For anyone who seek to verify a poll entry (e.g. explorers)

The commands are built to fit and play nicely within the cardano-cli. The poll and answers structures are based on transaction metadata and require to be embedded in an actual transaction. The added commands however only works from metadata and raw "GovernancePoll" envelopes.


Tutorial

Pre-requisites

Here's a quick tutorial on how to interactively submit an answer to an SPO
survey. To start, you must have received a valid survey in the form of a JSON
files properly constructed using the create-poll command. The file should
look roughly like the following:

poll.json

{
    "type": "GovernancePoll",
    "description": "An on-chain poll for SPOs: Pineapples on pizza?",
    "cborHex": "a1185ea2007450696e656170706c6573206f6e2070697a7a613f018263796573626e6f"
}

Warning

Surveys will be make 'official' using a signature from the genesis delegate
key; the signature is however not present in metadata but used as extra
signatory on the originating transaction.

Creating answer

From there, you can create a metadata entry to answer the survey using the
governance answer-poll command as such:

$ cardano-cli governance answer-poll --poll-file poll.json --signing-key-file key.secret

Note that:

  1. The key specified as --signing-key-file can/must be either a VRF secret
    key or a stake pool cold private key (Ed25519) in the JSON/Text-Envelope
    usual format expected by the cardano-cli. Here are an example of each:

    VRF key

    {
        "type": "VrfSigningKey_PraosVRF",
        "description": "VRF Signing Key",
        "cborHex": "5840b23fa897c1fc869d081e4818ea0ac533c1efaccb888cb57d8a40f6582783045d2dc2fa217af8b52251c4cdf538fa106cbf0b5beac3e74d05f97ceb33c0147a2c"
    }

    Cold key

    {
        "type": "StakePoolSigningKey_ed25519",
        "description": "Stake Pool Operator Signing Key",
        "cborHex": "58201d298ffa1544da0a5b2ea544728fc1ba7d2ae7c60e1d37da03895019740dd00a"
    }
  2. This command will prompt you to answer interactively. If you do not wish to
    be prompt interactively, you can use --answer with the index of the
    answer.

Running this command will display the survey in a human-readable form, and
prompt you for an answer, as shown below:

An on-chain poll for SPOs: Pineapples on pizza?
[0] Yes
[1] No

Please indicate an answer (by index): _

You can proceed by typing one of the possible answer index (here, 0 or 1)
and a newline. This will print witnessed metadata in the form of a JSON
detailed schema that shall then be posted on-chain in any transaction: the
easiest being to build a simple transaction to yourself carrying the metadata.

Here's an example of metadata using a VRF key and choosing the answer 0:

answer.json

{
  "94": {
    "map": [
      {
        "k": { "int": 2 },
        "v": { "bytes": "c9a077a1098d73498d17e9ea27045af820c311ced91f8c2bb9b5c7f446379063" }
      },
      {
        "k": { "int": 3 },
        "v": { "int": 0 }
      },
      {
        "k": { "int": 4 },
        "v": {
          "list": [
            { "bytes": "2dc2fa217af8b52251c4cdf538fa106cbf0b5beac3e74d05f97ceb33c0147a2c" },
            {
              "list": [
                { "bytes": "3606afd8d437a69a0c94ebff7fe57638bdb54bd03193c3c830808187a5d740222ab26fa3f1b7df8b22e846196dda1ee16d4d6e64c062129e07273ec36c06f453" },
                { "bytes": "06c46025c18deaa04aa45913e1c91906" }
              ]
            }
          ]
        }
      }
    ]
  }
}

Publishing answer

From there, you can use the transaction build command to create a transaction
to post on-chain. You'll need a signing key associated to a UTxO with enough
funds to carry the transaction (~0.2 Ada should you make a basic transaction to
yourself).

Assuming you have saved metadata produced from the previous step in a file
called answer.json, the command for building the transaction shall look like:

$ cardano-cli transaction build \
    --babbage-era \
    --cardano-mode \
    --mainnet \
    --tx-in ...#... \
    --change-address addr1... \
    --metadata-json-file answer.json \
    --json-metadata-detailed-schema \
    --out-file answer.tx

You'll need to fill-in --tx-in & --change-address with their corresponding
values. From there, you can sign answer.tx and submit the result as usual. If
everything goes well, the cardano-cli should display a transaction id that you
can track on-chain to ensure your reply to the survey was properly published.

Verifying Answers

Finally, it's possible to verify answers seen on-chain using the governance verify-poll command. What 'verify' means here is two-folds:

  • It checks that an answer is valid within the context of a given survey
  • It controls the witness (i.e. proof or signature) provided with it

Assuming you still have the original poll.json file, and some witnessed
metadata (as provided by the governance answer-poll command, and as found in
transactions on-chain) as answer.json, you can verify its validity via:

$ cardano-cli governance verify-poll --poll-file poll.json --metadata-file answer.json

On success, this should outputs:

Ok.

Otherwise, the command will formulate an issue with the answer and/or poll.

Commit split context: #5050 (comment)

KtorZ added 10 commits April 17, 2023 12:24
  Useful to have accessible from external modules that need to construct metadata values.
…alue

  It is quite common to need to construct long text or bytestring and, it is annoying to have to handle that at call-site every single time. Instead, we can provide smart constructors that takes care of splitting the text or byte string into reasonably-sized chunks. Note that we could also implement a version of those functions that is *more flexible* and only constructs chunks when needed; otherwise returning a plain MetaText or MetaBytes when they fit.

  For example, in CDDL, we would represent such a text string as:

  ```
  arbitrary_text =
    text .size (0..64) / [ * text .size (0..64) ]
  ```

  For the sake of keeping things simple however, those functions only implement the list variation.
  This module is really meant to be driven by the cardano-cli or any client implementation that seeks to (re-)implement the SPO on-chain poll functionality.
  This commit also introduces a new type-class 'AsTxMetadata' to hint to the fact that the chosen representation on-the-wire for those various types is a transaction metadata value. The serialization to CBOR becomes then straightforward once we've converted the type into a 'MetadataValue'. Similarly, the deserialization is made simpler by first deserializing an opaque 'MetadataValue', and then inspecting it to see if it has the expected shape.
  These commands are pretty straightforward to write by leveraging the newly introduced Cardano.Api.Governance.Poll API.
  One may ask why use sometimes files, sometimes stderr and sometimes stdout in implementing those commands.

  As a rule of thumb:

  - stdout = relevant content that should be structured to be piped into other tools or send to files
  - stderr = debug information useful to communicate context and details to users

  A command-line that outputs interactive debug information on stdout is arguably doing something wrong; unless it's the main terminal output of the application (e.g. printing structured logs on stdout).

  Then, why stdout rather than a file? Because I find the UX a lot better that way. Command lines with too many params are arguably hard to process; and using file as the medium of exchanges makes it harder / prevent piping into other tools easily. When printing structured results on stdout, one can always redirect the output to a file should they want it; so IMO stdout should always be the default; and files used only when necessary.

  Here I am only using an output file in the case of create-poll as a "build artifact". It allows to produce two outputs with distinct purpose; the file is meant to be shared as file, and thus it makes sense to treat it as such from the CLI as well. It also makes it clearer for users (even though that's going to be only super users here) what is meant to be shared and what is metadata.
  Mostly plumbing and parser implementation following what already exists.
  This is meant as a way to assert on *expected failures*! Sadly, `try` or other exception handling mechanisms do not work inside of the `TestT` monad, so I had to extract and lift the error to be able to catch it and assert on it. Yet, I need to assert on failures and thus, failures should not crash the test early but be assertable as a possible execution outcome.

  There's maybe something more clever to do but I only had a day and a half to spend on all this so I'd rather "get it done".
…mands

  Fixture keys were generated using the command-line itself. The set of tests cover quite extensively the various commands, as well as a few 'negative' test scenarios. It is more complicated to cover the interactive part of the 'answer-poll' command through those tests; and this is therefore left as manual test. Instructions for executing the sequence will also be provided with the introduction of the commands (e.g. in the description of [PR#5050](#5050).
… Witness}

  ```
  roundtrip GovernancePoll CBOR:                        OK (0.09s)
      ✓ roundtrip GovernancePoll CBOR passed 100 tests.
  roundtrip GovernancePollAnswer CBOR:                  OK
      ✓ roundtrip GovernancePollAnswer CBOR passed 100 tests.
  roundtrip GovernancePollWitness CBOR:                 OK (0.01s)
      ✓ roundtrip GovernancePollWitness CBOR passed 100 tests.
  ```
  ```
  Cardano.Api
    Test.Cardano.Api.Metadata
      valid & rountrip text chunks:  OK (0.03s)
          ✓ valid & roundtrip text chunks passed 100 tests.
            Empty chunks   3% ▌··················· ✓  1%
            Single chunks 26% █████▏·············· ✓  5%
            Many chunks   71% ██████████████▏····· ✓ 25%
      valid & rountrip bytes chunks: OK
          ✓ valid & roundtrip bytes chunks passed 100 tests.
            Empty chunks   3% ▌··················· ✓  1%
            Single chunks 55% ███████████········· ✓  5%
            Many chunks   42% ████████▍··········· ✓ 25%
  ```

  Turns out there were two issues:

  - Empty {text,byte}strings would generate a singleton chunk with an
    empty value; which is okay semantically but ugly; empty strings now
    generate an empty chunk.

  - Metadata values measure the length of UTF-8-encoded strings, which
    means we can't rely on default text functions to split a text
    string. This is likely an overkill in many situation in the context
    of PR#5050 since most questions / answers will be in plain english.

    However, we can now put emojis and crazy unicode characters in there
    without problems.
@Jimbo4350 Jimbo4350 enabled auto-merge April 17, 2023 16:40
@Jimbo4350 Jimbo4350 added this pull request to the merge queue Apr 17, 2023
Merged via the queue into master with commit cf61eb3 Apr 17, 2023
@iohk-bors iohk-bors bot deleted the jordan/cip-0094-on-chain-spo-polls branch April 17, 2023 16:56
Copy link
Contributor

@dcoutts dcoutts left a comment

Choose a reason for hiding this comment

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

For what it's worth, I'm not happy with the CIP design, but that's a separate issue I suppose.

cardano-foundation/CIPs#496 (review)

One comment below about fishy behaviour in the VRF verification.

isValid =
case witness of
GovernancePollWitnessVRF vk proof ->
VRF.verifyVRF () vk answer (undefined, proof)
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 fishy. It's at minimum being unreasonably cozy with the implementation, rather than relying on the class interface.

Copy link
Contributor

@KtorZ KtorZ Apr 18, 2023

Choose a reason for hiding this comment

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

Yes this is a slight abuse of the interface because the current class API requires the Output (the undefined term here) without making any use of it since it is actually irrelevant to the verification process. Since in this scenario we do not even care about the outputs (as you're rightfully pointing out in the CIP's comment), I didn't feel like bothering end-users with them either just for the sake of satisfying an interface that's arguably not restrictive enough.

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.

4 participants