diff --git a/Cargo.lock b/Cargo.lock index 875670d0ab9..6bd518ab46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4597,13 +4597,19 @@ version = "0.1.11" dependencies = [ "anyhow", "async-trait", + "ciborium", + "fixed", + "hex", "httpmock", "mithril-aggregator-client", + "mithril-cardano-node-chain", "mithril-common", + "serde", "serde_json", "slog", "slog-async", "slog-term", + "thiserror 2.0.18", "tokio", ] diff --git a/docs/runbook/protocol-configuration-markers/README.md b/docs/runbook/protocol-configuration-markers/README.md new file mode 100644 index 00000000000..8f408213a5b --- /dev/null +++ b/docs/runbook/protocol-configuration-markers/README.md @@ -0,0 +1,396 @@ +# Store Mithril Protocol Configuration Markers On Chain + +**This is a work in progress** :hammer_and_wrench: + +This is the process for storing Mithril Protocol Configuration markers on the Cardano chain (bootstrap and update operations). + +> [!IMPORTANT] +> :fire: The process described in this document can lead to disturbed service of the associated Mihtril network. Thus it should be manipulated by experts only. + +## Pre-requisites + +- You need to have a recent version of [`jq`](https://stedolan.github.io/jq/download/) running (1.6+) +- A running Cardano node running locally on the network you are targeting +- A running Mithril Aggregator node +- The protocol confoiguration activation marker Cardano payment keypairs of your Mithril network +- The protocol confoiguration secret key of your Mithril network + +## Setup + +### Setup environment variables + +Export the environment variables needed to complete the process: + +```bash +export CARDANO_CLI=**CARDANO_CLI_COMMAND** +export CARDANO_NODE_SOCKET_PATH=**PATH_TO_YOUR_NODE_SOCKET** +export CARDANO_WALLET_PATH=**PATH_TO_YOUR_KEYPAIRS** +export PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY=**YOUR_PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY** +export ASSETS_PATH=**YOUR_ASSETS_PATH** +export SCRIPT_TX_VALUE=**MINIUM_SCRIPT_TX_VALUE** +``` + +A common value for the transaction amount used when a script transaction is made is: + +```bash +export SCRIPT_TX_VALUE=2100000 +``` + +Compute the network magic parameter that handles both the Cardano mainnet and Cardano test networks: + +- If this is for a testing network + +```bash +export CARDANO_TESTNET_MAGIC=**YOUR_TESTNET_MAGIC** +export CARDANO_NETWORK_MAGIC="--testnet-magic $CARDANO_TESTNET_MAGIC" +``` + +- If this is for the `mainnet` + +```bash +export CARDANO_NETWORK_MAGIC="--mainnet" +``` + +Compute the current Cardano era: + +```bash +export CARDANO_ERA=$($CARDANO_CLI query tip $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH | jq -r '.era |= ascii_downcase | .era') +``` + +### Prepare protocol configuration markers + +> [!IMPORTANT] +> :fire: A misconfiguration of the protocol configuration markers can cause disturbed service on a Mithril network. + +#### Exporting last active protocol configurations markers from the chain in a JSON file + +Export latest active configurations from the Cardano chain: + +```bash +./mithril-aggregator protocol-configuration export-markers --protocol-configuration-markers-secret-key $PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY --target-path $ASSETS_PATH/protocol-configurations.json +``` + +#### Modifying protocol configurations + +Manually edit previously exported JSON file written at `$ASSETS_PATH/protocol-configurations.json` + +> [!IMPORTANT] +> :fire: Make sur to keep at least the greater epoch's configuration without any modification. +> add a new Epoch configuration (at least lastest epoch + 3) + +#### Generate Tx Datum payload file + +Generate Tx Datum payload file by using previously edited JSON file + +```bash +./mithril-aggregator protocol-configuration generate-tx-datum --protocol-configuration-markers-secret-key $PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY --import-path $ASSETS_PATH/protocol-configurations.json --target-path $ASSETS_PATH/protocol-configurations-datum.json +``` + +#### Verify the produced protocol configuration marker + +Verify that the produced protocol configuration markers are exactly what is expected with the following command: + +```bash +cat $ASSETS_PATH/protocol-configurations-datum.json| jq -r '.fields[].bytes' | tr '\n' ' ' | xxd -r -p | jq +``` + +An example output of the command is: + +```json +{ + "markers": [ + { + "epoch": 42, + "configuration": "a37370726f746f636f6c5f706172616d6574657273a3616b09616d184d657068695f66f93800781b656e61626c65645f7369676e65645f656e746974795f74797065738378184d69746872696c5374616b65446973747269627574696f6e6f43617264616e6f44617461626173657343617264616e6f5472616e73616374696f6e73781a7369676e65645f656e746974795f74797065735f636f6e666967a27463617264616e6f5f7472616e73616374696f6e73a27273656375726974795f706172616d65746572186464737465700a781b63617264616e6f5f626c6f636b735f7472616e73616374696f6e73f6" + }, + { + "epoch": 43, + "configuration": "a37370726f746f636f6c5f706172616d6574657273a3616b0a616d1858657068695f66fb3fe6666666666666781b656e61626c65645f7369676e65645f656e746974795f74797065738478184d69746872696c5374616b65446973747269627574696f6e6f43617264616e6f44617461626173657343617264616e6f5472616e73616374696f6e73781943617264616e6f426c6f636b735472616e73616374696f6e73781a7369676e65645f656e746974795f74797065735f636f6e666967a27463617264616e6f5f7472616e73616374696f6e73a27273656375726974795f706172616d65746572186464737465700a781b63617264616e6f5f626c6f636b735f7472616e73616374696f6e73a27273656375726974795f706172616d657465721896647374657014" + } + ], + "signature": "16d6e2fcdce9ad24c27a80b0a93ae912a9561660dc100ef628274d5ea0de76d8328282cf29f119da4d9cfae16c015b3138af27561eb5d75ac2c9fbf5629d3d0e" +} + +``` + +TODO add a step to decode cbor for verification ? like using cbor-diag command in the jq command ? + +## Bootstrap Era Markers: Write a transaction with the first version of datum on chain + +> [!IMPORTANT] +> :fire: This step must be done only once for an address when no prior datum has been written in a UTxO. +> Otherwise, you need to refer to this [section](#update-era-markers-write-a-new-version-of-datum-on-chain) + +Verify that the payment address has funds: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash + TxHash TxIx Amount +-------------------------------------------------------------------------------------- +f0c0345f151f9365fbbb4e7afa217e56b987d9e91fd754ca609d9dfec97275c7 0 10000000000 lovelace + TxOutDatumNone +``` + +And create the variable `TX_IN={TxHash}#{TxIn}` by replacing with values from the previous command: + +```bash +TX_IN=f0c0345f151f9365fbbb4e7afa217e56b987d9e91fd754ca609d9dfec97275c7#0 +``` + +Now create the bootstrap transaction with datum: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction build $CARDANO_NETWORK_MAGIC \ + --tx-in $TX_IN \ + --tx-out $(cat $CARDANO_WALLET_PATH/payment.addr)+$SCRIPT_TX_VALUE \ + --tx-out-inline-datum-file $ASSETS_PATH/mithril-era-datum.json \ + --change-address $(cat $CARDANO_WALLET_PATH/payment.addr) \ + --out-file $ASSETS_PATH/tx.raw \ + --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash +Estimated transaction fee: Lovelace 168669 +``` + +Then sign the transaction: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction sign \ + --tx-body-file $ASSETS_PATH/tx.raw \ + --signing-key-file $CARDANO_WALLET_PATH/payment.skey \ + $CARDANO_NETWORK_MAGIC \ + --out-file $ASSETS_PATH/tx.signed +``` + +And submit it: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction submit \ + $CARDANO_NETWORK_MAGIC \ + --tx-file $ASSETS_PATH/tx.signed \ + --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash +Transaction successfully submitted. +``` + +Also get the transaction id: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction txid --tx-file $ASSETS_PATH/tx.signed +``` + +```bash +6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb +``` + +We need to wait a few seconds before the transaction is available and we can see the initial datum for the script address: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash + TxHash TxIx Amount +-------------------------------------------------------------------------------------- +6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb 0 1500000 lovelace + TxOutDatumInline ReferenceTxInsScriptsInlineDatumsInBabbageEra (ScriptDataConstructor 0 [ScriptDataBytes "[{\"n\":\"thales\",\"e\":1}]",ScriptDataBytes "\165\143\232\227\&6\244e\222\211\187\167\197\167\175\229\181\162o/\182[|Nnt.h\ACKE\241=\242\139\242\182:a\204r\217\200&\190I\SO,\US\DLE\152\217U\223P5\128\164\232\153\181\ETB8\132\227\SO"]) +6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb 1 9998327415 lovelace + TxOutDatumNone +``` + +Optional: We can retrieve the initial value stored in the datum with the cardano cli: + +The full utxo json representation: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH --out-file temp.json && cat temp.json | jq '.[] | select(.inlineDatum | . != null and . != "")' +``` + +```bash +{ + "address": "addr_test1qzzngukkj9ydjemqjlgfn42sevy2xnvauay46weushlpuq9thd4ray00csjssf4sxftv04xeequ3xfx72nujg9y4d5ysgkxxlh", + "datum": null, + "inlineDatum": { + "constructor": 0, + "fields": [ + { + "bytes": "5b7b226e223a227468616c6573222c2265223a317d5d" + }, + { + "bytes": "a58fe8e336f465ded3bba7c5a7afe5b5a26f2fb65b7c4e6e742e680645f13df28bf2b63a61cc72d9c826be490e2c1f1098d955df503580a4e899b5173884e30e" + } + ] + }, + "inlineDatumhash": "d8c1865816a520b92aa19ac5bc295fb1996281adf5ff06366343c6432d591cd1", + "referenceScript": null, + "value": { + "lovelace": 1500000 + } +} +``` + +The parsed era markers json representation: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH --out-file temp.json && cat temp.json | jq -r '.[] | select(.inlineDatum | . != null and . != "")| .inlineDatum.fields[].bytes' | tr '\n' ' ' | xxd -r -p | jq +``` + +```json +{ + "markers": [ + { + "name": "thales", + "epoch": 1 + }, + { + "name": "pythagoras", + "epoch": null + } + ], + "signature": "a83a8dee3b875a7e8d259500a8ce14cc73587ef838899d269ad58aadd16086cfe0486528e54b841b3a1d5aa8b7176d55c0803337ca59fbd3654b2bdd5a480d05" +} +``` + +## Update Era Markers: Write a new version of datum on chain + +> [!IMPORTANT] +> :fire: This step must be used anytime the era markers must be updated on chain for an address when prior datum has already been written in a UTxO. +> Otherwise, you need to refer to this [section](#bootstrap-era-markers-write-a-transaction-with-the-first-version-of-datum-on-chain) + +Retrieve the utxo of the payment address: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash + TxHash TxIx Amount +-------------------------------------------------------------------------------------- +6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb 0 1500000 lovelace + TxOutDatumInline ReferenceTxInsScriptsInlineDatumsInBabbageEra (ScriptDataConstructor 0 [ScriptDataBytes "[{\"n\":\"thales\",\"e\":1}]",ScriptDataBytes "\165\143\232\227\&6\244e\222\211\187\167\197\167\175\229\181\162o/\182[|Nnt.h\ACKE\241=\242\139\242\182:a\204r\217\200&\190I\SO,\US\DLE\152\217U\223P5\128\164\232\153\181\ETB8\132\227\SO"]) +6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb 1 9998327415 lovelace + TxOutDatumNone +``` + +And create the variable `TX_IN_DATUM={TxHash}#{TxIn}` by replacing with values from the previous command (where inline datumn are available): + +```bash +TX_IN_DATUM=6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb#0 +``` + +And create the variable `TX_IN_NO_DATUM={TxHash}#{TxIn}` by replacing with values from the previous command (where inline datumn are not available): + +```bash +TX_IN_NO_DATUM=6518b3cea0b49b55746ec61148e7c60ab042959d534f6bb6e8f6a844d4af69fb#1 +``` + +Now create the update transaction with datum: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction build $CARDANO_NETWORK_MAGIC \ + --tx-in $TX_IN_DATUM \ + --tx-in $TX_IN_NO_DATUM \ + --tx-out $(cat $CARDANO_WALLET_PATH/payment.addr)+$SCRIPT_TX_VALUE \ + --tx-out-inline-datum-file $ASSETS_PATH/mithril-era-datum.json \ + --change-address $(cat $CARDANO_WALLET_PATH/payment.addr) \ + --out-file $ASSETS_PATH/tx.raw \ + --socket-path $CARDANO_NODE_SOCKET_PATH +Estimated transaction fee: Lovelace 179889 +``` + +Then sign the transaction: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction sign \ + --tx-body-file $ASSETS_PATH/tx.raw \ + --signing-key-file $CARDANO_WALLET_PATH/payment.skey \ + $CARDANO_NETWORK_MAGIC \ + --out-file $ASSETS_PATH/tx.signed +``` + +And submit it: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction submit \ + $CARDANO_NETWORK_MAGIC \ + --tx-file $ASSETS_PATH/tx.signed \ + --socket-path $CARDANO_NODE_SOCKET_PATH +Transaction successfully submitted. +``` + +Also get the transaction id: + +```bash +$CARDANO_CLI $CARDANO_ERA transaction txid --tx-file $ASSETS_PATH/tx.signed +``` + +```bash +1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8 +``` + +We need to wait a few seconds before the transaction is available and we can see the updated datum for the script address: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH +``` + +```bash + TxHash TxIx Amount +-------------------------------------------------------------------------------------- +1f139b47017c9c90d4622ac768e249d25d37ad4461db44a20486b7da72a78915 0 2000000 lovelace + TxOutDatumInline ReferenceTxInsScriptsInlineDatumsInBabbageEra (ScriptDataConstructor 0 [ScriptDataBytes "[{\"n\":\"thales\",\"e\":1},{\"n\":\"pythagoras\",\"e\":null}]",ScriptDataBytes "^P\EOT\248k3\196/\139\tU\173H\138\FS\194MD\240\153\227\142z\181\134\213\168\&2\222\219i1\246\NAK\\]\247\154U\143-^vmtq\204\207#\236\213\f\201\&1\152\145(\161\ETX;\183\128\195\r"]) +1f139b47017c9c90d4622ac768e249d25d37ad4461db44a20486b7da72a78915 1 9997647526 lovelace + TxOutDatumNone +``` + +We can retrieve the updated value stored in the datum with the cardano cli: + +The full utxo json representation: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH --out-file temp.json && cat temp.json | jq '.[] | select(.inlineDatum | . != null and . != "")' +``` + +```bash +{ + "address": "addr_test1qzzngukkj9ydjemqjlgfn42sevy2xnvauay46weushlpuq9thd4ray00csjssf4sxftv04xeequ3xfx72nujg9y4d5ysgkxxlh", + "datum": null, + "inlineDatum": { + "constructor": 0, + "fields": [ + { + "bytes": "5b7b226e223a227468616c6573222c2265223a317d2c7b226e223a227079746861676f726173222c2265223a6e756c6c7d5d" + }, + { + "bytes": "5e5004f86b33c42f8b0955ad488a1cc24d44f099e38e7ab586d5a832dedb6931f6155c5df79a558f2d5e766d7471cccf23ecd50cc931989128a1033bb780c30d" + } + ] + }, + "inlineDatumhash": "021310e8764d7d7ec3d66c00792ff391fa2145e1c8328eaf4630734c43bcfedc", + "referenceScript": null, + "value": { + "lovelace": 2000000 + } +} +``` + +The parsed era markers json representation: + +```bash +$CARDANO_CLI $CARDANO_ERA query utxo --address $(cat $CARDANO_WALLET_PATH/payment.addr) $CARDANO_NETWORK_MAGIC --socket-path $CARDANO_NODE_SOCKET_PATH --out-file temp.json && cat temp.json | jq -r '.[] | select(.inlineDatum | . != null and . != "")| .inlineDatum.fields[].bytes' | tr '\n' ' ' | xxd -r -p | jq +``` + +```json +{ + "markers": [ + { + "name": "thales", + "epoch": 1 + }, + { + "name": "pythagoras", + "epoch": 123 + } + ], + "signature": "a83a8dee3b875a7e8d259500a8ce14cc73587ef838899d269ad58aadd16086cfe0486528e54b841b3a1d5aa8b7176d55c0803337ca59fbd3654b2bdd5a480d05" +} +``` diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index d987c9f3437..9325df1111d 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -12,13 +12,19 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +ciborium = "0.2.2" +fixed = "1.31.0" +hex = { workspace = true } mithril-aggregator-client = { path = "../mithril-aggregator-client" } +mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-common = { path = "../../mithril-common" } +serde = { workspace = true } +serde_json = { workspace = true } slog = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } [dev-dependencies] httpmock = "0.8.3" -serde_json = { workspace = true } slog-async = { workspace = true } slog-term = { workspace = true } diff --git a/internal/mithril-protocol-config/src/cardano_chain/message.rs b/internal/mithril-protocol-config/src/cardano_chain/message.rs new file mode 100644 index 00000000000..d0085bb7689 --- /dev/null +++ b/internal/mithril-protocol-config/src/cardano_chain/message.rs @@ -0,0 +1,206 @@ +use anyhow::Context; +use fixed::types::U8F24; +use hex::FromHex; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; +use thiserror::Error; + +use mithril_common::{ + StdError, + entities::{ + BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, + }, +}; + +/// The cbor representation of a [ProtocolConfigurationForEpochMessage] +pub type CborProtocolConfigurationForEpochMessage = String; + +/// Value object that represents a tag of Protocol Configuration. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProtocolConfigurationMarker { + /// Epoch + pub epoch: Epoch, + + /// Protocol parameters + pub configuration: CborProtocolConfigurationForEpochMessage, +} + +impl ProtocolConfigurationMarker { + /// instantiate a new [ProtocolConfigurationMarker]. + pub fn new( + epoch: Epoch, + protocol_configuration: CborProtocolConfigurationForEpochMessage, + ) -> Self { + ProtocolConfigurationMarker { + epoch, + configuration: protocol_configuration, + } + } +} + +/// Parse error +#[derive(Error, Debug)] +#[error("Codec parse error")] +pub struct ProtocolConfigurationForEpochMessageParseError(#[source] StdError); + +/// Protocol cryptographic parameters Message +/// +/// used for the CBOR representation of [ProtocolConfigurationForEpochMessage] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProtocolParametersMessage { + /// Quorum parameter + pub k: u64, + + /// Security parameter (number of lotteries) + pub m: u64, + + /// f in phi(w) = 1 - (1 - f)^w, where w is the stake of a participant + pub phi_f: f64, +} + +impl ProtocolParametersMessage { + /// phi_f_fixed is a fixed decimal representation of phi_f + /// used for PartialEq and Hash implementation + pub fn phi_f_fixed(&self) -> U8F24 { + U8F24::from_num(self.phi_f) + } +} + +impl PartialEq for ProtocolParametersMessage { + fn eq(&self, other: &ProtocolParametersMessage) -> bool { + self.k == other.k && self.m == other.m && self.phi_f_fixed() == other.phi_f_fixed() + } +} + +impl From for ProtocolParametersMessage { + fn from(params: ProtocolParameters) -> Self { + ProtocolParametersMessage { + k: params.k, + m: params.m, + phi_f: params.phi_f, + } + } +} + +/// Configuration for the signing of Cardano transactions +/// +/// used for the CBOR representation of [ProtocolConfigurationForEpochMessage] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CardanoTransactionsSigningConfigMessage { + /// Number of blocks to discard from the tip of the chain when importing transactions. + pub security_parameter: BlockNumberOffset, + + /// The number of blocks between signature of the transactions. + pub step: BlockNumber, +} + +impl From for CardanoTransactionsSigningConfigMessage { + fn from(config: CardanoTransactionsSigningConfig) -> Self { + CardanoTransactionsSigningConfigMessage { + security_parameter: config.security_parameter, + step: config.step, + } + } +} + +/// Configuration for the signing of Cardano blocks and transactions +/// +/// used for the CBOR representation of [ProtocolConfigurationForEpochMessage] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CardanoBlocksTransactionsSigningConfigMessage { + /// Number of blocks to discard from the tip of the chain when importing blocks and transactions. + pub security_parameter: BlockNumberOffset, + + /// The number of blocks between signature of the blocks and transactions. + pub step: BlockNumber, +} + +impl From + for CardanoBlocksTransactionsSigningConfigMessage +{ + fn from(config: CardanoBlocksTransactionsSigningConfig) -> Self { + CardanoBlocksTransactionsSigningConfigMessage { + security_parameter: config.security_parameter, + step: config.step, + } + } +} + +//A epoch configuration used for the CBOR representation in the [ProtocolConfigurationMarker] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +/// A network configuration available for an epoch +pub struct ProtocolConfigurationForEpochMessage { + /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) + pub protocol_parameters: ProtocolParametersMessage, + + /// List of available types of certifications + pub enabled_signed_entity_types: BTreeSet, //todo use instead the forward compatible type + + /// Signing configuration for Cardano transactions + pub cardano_transactions: Option, + + /// Signing configuration for Cardano blocks and transactions + pub cardano_blocks_transactions: Option, +} + +impl ProtocolConfigurationForEpochMessage { + /// Serialize the structure to a CBOR bytes representation. + fn to_cbor_bytes(&self) -> Result, ProtocolConfigurationForEpochMessageParseError> { + let mut cursor = std::io::Cursor::new(Vec::new()); + ciborium::ser::into_writer(&self, &mut cursor) + .with_context(|| "ProtocolConfigurationForEpoch can not serialize data to cbor") + .map_err(ProtocolConfigurationForEpochMessageParseError)?; + + Ok(cursor.into_inner()) + } + + /// Serialize the structure to a CBOR hex representation. + pub fn to_cbor_hex(&self) -> Result { + Ok(hex::encode(self.to_cbor_bytes()?)) + } + + /// Deserialize a type `T: Serialize + DeserializeOwned` from CBOR bytes representation. + fn from_cbor_bytes( + bytes: &[u8], + ) -> Result { + let mut cursor = std::io::Cursor::new(&bytes); + let a: Self = ciborium::de::from_reader(&mut cursor) + .with_context(|| "ProtocolConfigurationForEpoch can not unserialize cbor data") + .map_err(ProtocolConfigurationForEpochMessageParseError)?; + + Ok(a) + } + + /// Deserialize a type `T: Serialize + DeserializeOwned` from CBOR hex representation. + pub fn from_cbor_hex( + hex: &str, + ) -> Result { + let hex_vector = Vec::from_hex(hex) + .with_context(|| "ProtocolConfigurationForEpochMessage can not unserialize hex data") + .map_err(ProtocolConfigurationForEpochMessageParseError)?; + + Self::from_cbor_bytes(&hex_vector) + .with_context(|| "ProtocolConfigurationForEpochMessage can not unserialize cbor data") + .map_err(ProtocolConfigurationForEpochMessageParseError) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::test::double::Dummy; + + use super::*; + + #[test] + fn to_cbor_from_cbor_conversion() { + let mithril_network_configuration_for_epoch = ProtocolConfigurationForEpochMessage::dummy(); + let cbor = mithril_network_configuration_for_epoch.to_cbor_hex().unwrap(); + let mithril_network_configuration_for_epoch_from_cbor = + ProtocolConfigurationForEpochMessage::from_cbor_hex(&cbor).unwrap(); + assert_eq!( + mithril_network_configuration_for_epoch, + mithril_network_configuration_for_epoch_from_cbor + ); + } +} diff --git a/internal/mithril-protocol-config/src/cardano_chain/mod.rs b/internal/mithril-protocol-config/src/cardano_chain/mod.rs new file mode 100644 index 00000000000..29a3ff03187 --- /dev/null +++ b/internal/mithril-protocol-config/src/cardano_chain/mod.rs @@ -0,0 +1,8 @@ +pub mod message; +pub mod payload; +pub mod protocol_configuration_reader; + +pub use payload::{ + ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain, + SignedProtocolConfigurationMarkersPayload as SignedProtocolConfigurationMarkersPayloadCardanoChain, +}; diff --git a/internal/mithril-protocol-config/src/cardano_chain/payload.rs b/internal/mithril-protocol-config/src/cardano_chain/payload.rs new file mode 100644 index 00000000000..50d406555f6 --- /dev/null +++ b/internal/mithril-protocol-config/src/cardano_chain/payload.rs @@ -0,0 +1,98 @@ +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use mithril_common::crypto_helper::{ + ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSignature, + key_encode_hex, +}; +use mithril_common::{StdError, StdResult}; + +use crate::cardano_chain::message::ProtocolConfigurationMarker; + +/// [ProtocolConfigurationMarkersPayload] related errors. +#[derive(Debug, Error)] +pub enum ProtocolConfigurationMarkersPayloadError { + /// Error raised when the message serialization fails + #[error("could not serialize message")] + SerializeMessage(#[source] StdError), + + /// Error raised when the signature deserialization fails + #[error("could not deserialize signature")] + DeserializeSignature(#[source] StdError), + + /// Error raised when the signature is missing + #[error("could not verify signature: signature is missing")] + MissingSignature, + + /// Error raised when the signature is invalid + #[error("could not verify signature")] + VerifySignature(#[source] StdError), + + /// Error raised when the signing the markers + #[error("could not create signature")] + CreateSignature(#[source] StdError), +} + +/// Protocol Configuration markers payload +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProtocolConfigurationMarkersPayload { + /// List of protocol configuration markers + pub markers: Vec, +} + +/// Signed Protocol Configuration markers payload +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SignedProtocolConfigurationMarkersPayload { + /// List of protocol configuration markers + pub markers: Vec, + + /// Protocol Configuration markers signature + pub signature: ProtocolConfigurationMarkersVerifierSignature, +} + +impl SignedProtocolConfigurationMarkersPayload { + /// Instanciate a new SignedProtocolConfigurationMarkersPayload with markers and signature + pub fn new( + markers: Vec, + signature: ProtocolConfigurationMarkersVerifierSignature, + ) -> Self { + Self { markers, signature } + } + + /// Encode this payload to a json hex string + pub fn to_json_hex(&self) -> StdResult { + key_encode_hex(self).with_context( + || "SignedProtocolConfigurationMarkersPayload could not be json hex encoded", + ) + } +} + +impl ProtocolConfigurationMarkersPayload { + /// Instanciate a new ProtocolConfigurationMarkersPayload with markers + pub fn new(markers: Vec) -> Self { + Self { markers } + } + + fn message_to_bytes(&self) -> Result, ProtocolConfigurationMarkersPayloadError> { + serde_json::to_vec(&self.markers) + .map_err(|e| ProtocolConfigurationMarkersPayloadError::SerializeMessage(e.into())) + } + + /// Sign an protocol configuration markers payload + pub fn sign( + self, + signer: &ProtocolConfigurationMarkersSigner, + ) -> Result + { + let signature = + signer.sign(&self.message_to_bytes().map_err(|e| { + ProtocolConfigurationMarkersPayloadError::CreateSignature(e.into()) + })?); + + Ok(SignedProtocolConfigurationMarkersPayload { + markers: self.markers, + signature, + }) + } +} diff --git a/internal/mithril-protocol-config/src/cardano_chain/protocol_configuration_reader.rs b/internal/mithril-protocol-config/src/cardano_chain/protocol_configuration_reader.rs new file mode 100644 index 00000000000..51d565b2f0a --- /dev/null +++ b/internal/mithril-protocol-config/src/cardano_chain/protocol_configuration_reader.rs @@ -0,0 +1,45 @@ +use async_trait::async_trait; +use std::collections::BTreeMap; +use std::sync::Arc; + +use mithril_cardano_node_chain::chain_observer::ChainObserver; +use mithril_cardano_node_chain::entities::ChainAddress; +use mithril_common::StdResult; +use mithril_common::crypto_helper::ProtocolConfigurationMarkersVerifierVerificationKey; + +use crate::interface::ProtocolConfigurationMarkersReader; +use crate::model::ConfigurationComputerFromMarkers; + +/// Cardano Chain reader retrieves protocol configuration markers on chain +pub struct CardanoChainProtocolConfigurationMarkersReader { + address: ChainAddress, + chain_observer: Arc, + verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, +} + +impl CardanoChainProtocolConfigurationMarkersReader { + /// CardanoChainAdapter factory + pub fn new( + address: ChainAddress, + chain_observer: Arc, + verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, + ) -> Self { + Self { + address, + chain_observer, + verification_key, + } + } +} + +#[async_trait] +impl ProtocolConfigurationMarkersReader for CardanoChainProtocolConfigurationMarkersReader { + async fn read(&self) -> StdResult { + //TODO to implement + //read payload + // to ProtocolConfigurationForEpochMessage + // to ProtocolConfigurationForEpoch + // build ConfigurationComputerFromMarkers with ProtocolConfigurationForEpoch + Ok(ConfigurationComputerFromMarkers::new(BTreeMap::new())) + } +} diff --git a/internal/mithril-protocol-config/src/interface.rs b/internal/mithril-protocol-config/src/interface.rs index 099a0450eca..c34702c2357 100644 --- a/internal/mithril-protocol-config/src/interface.rs +++ b/internal/mithril-protocol-config/src/interface.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use mithril_common::{StdResult, entities::Epoch}; -use crate::model::MithrilNetworkConfiguration; +use crate::model::{ConfigurationComputerFromMarkers, MithrilNetworkConfiguration}; /// A provider for the Mithril network configuration of the a given epoch. #[async_trait] @@ -14,3 +14,10 @@ pub trait MithrilNetworkConfigurationProvider: Sync + Send { epoch: Epoch, ) -> StdResult; } + +/// A reader for Protocol Configuration Markers +#[async_trait] +pub trait ProtocolConfigurationMarkersReader: Sync + Send { + /// Read protocol configuration markers from the underlying adapter. + async fn read(&self) -> StdResult; +} diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index b03e1691ad0..88caae5f36c 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -1,6 +1,7 @@ #![warn(missing_docs)] //! This crate provides mechanisms to read and check the configuration parameters of a Mithril network. +pub mod cardano_chain; pub mod http; pub mod interface; pub mod model; diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index 6c898fb4e9a..38e4d8ce795 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -1,6 +1,7 @@ -//! Model definitions for Mithril Protocol Configuration. +//! Model definitions for Mithril Protocol Configuration and ProtocolConfigurationMarkersReader -use std::collections::BTreeSet; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet}; use mithril_common::{ entities::{ @@ -10,7 +11,7 @@ use mithril_common::{ messages::{ProtocolConfigurationMessage, SignedEntityTypeDiscriminantsMessage}, }; -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] /// Custom configuration for the signed entity types pub struct SignedEntityTypeConfiguration { @@ -38,7 +39,7 @@ pub struct MithrilNetworkConfiguration { } //A epoch configuration -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] /// A network configuration available for an epoch pub struct MithrilNetworkConfigurationForEpoch { /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) @@ -67,6 +68,46 @@ impl From for MithrilNetworkConfigurationForEpoch } } +//A epoch configuration used by the reader +#[derive(PartialEq, Clone, Debug)] +/// A network configuration available for an epoch +pub struct ProtocolConfigurationForEpoch { + /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) + pub protocol_parameters: ProtocolParameters, + + /// List of available types of certifications + pub enabled_signed_entity_types: BTreeSet, + + //TODO do we want a sub struct like signed_entity_types_config in model ? + /// Signing configuration for Cardano transactions + pub cardano_transactions: Option, + + /// Signing configuration for Cardano blocks and transactions + pub cardano_blocks_transactions: Option, +} + +/// [ConfigurationComputerFromMarkers] containing markers by epoch +#[derive(Default, PartialEq, Clone, Debug)] +pub struct ConfigurationComputerFromMarkers { + /// BTreeMap assotiation of ProtocolConfigurationForEpoch to a coresponding Epoch + pub markers: BTreeMap, +} + +impl ConfigurationComputerFromMarkers { + /// Create a new [ConfigurationComputerFromMarkers] with the given markers. + pub fn new(markers: BTreeMap) -> Self { + Self { markers } + } + + /// retrieve configuration for given Epoch or fallback to last known configuration + pub fn get_network_configuration(&self, epoch: Epoch) -> Option { + self.markers + .range(..=epoch) + .next_back() + .map(|(_, marker)| marker.clone()) + } +} + #[cfg(test)] mod tests { use mithril_common::messages::{ @@ -98,4 +139,80 @@ mod tests { network_config.enabled_signed_entity_types, ); } + + mod configuration_computer_from_markers { + + use super::*; + + fn fake_config_for_epoch(epoch: Epoch) -> ProtocolConfigurationForEpoch { + ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters::new(*epoch, *epoch, 0.1), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + } + } + + #[derive(Debug)] + struct TestCase { + requested_epoch: Epoch, + expected_conf_epoch: Epoch, + } + + macro_rules! test_case { + ( + requested: $requested_epoch:expr, + expected: $expected_conf_epoch:expr + ) => { + TestCase { + requested_epoch: Epoch($requested_epoch), + expected_conf_epoch: Epoch($expected_conf_epoch), + } + }; + } + + #[test] + fn test_get_network_configuration_must_fallback_to_last_known_configuration_if_epoch_not_found() + { + let markers = BTreeMap::from([ + (Epoch(2), fake_config_for_epoch(Epoch(2))), + (Epoch(6), fake_config_for_epoch(Epoch(6))), + (Epoch(10), fake_config_for_epoch(Epoch(10))), + ]); + + fn test_cases() -> Vec { + vec![ + test_case!(requested: 3, expected: 2 ), + test_case!(requested: 5, expected: 2 ), + test_case!(requested: 6, expected: 6 ), + test_case!(requested: 7, expected: 6 ), + test_case!(requested: 9, expected: 6 ), + test_case!(requested: 10, expected: 10), + test_case!(requested: 11, expected: 10), + test_case!(requested: 12, expected: 10), + ] + } + + let configurations = ConfigurationComputerFromMarkers::new(markers); + + for test_case in test_cases() { + assert_eq!( + configurations.get_network_configuration(test_case.requested_epoch), + Some(fake_config_for_epoch(test_case.expected_conf_epoch)) + ); + } + } + + #[test] + fn test_get_network_configuration_return_none_if_no_fallback_conf_is_available() { + let markers = BTreeMap::from([ + (Epoch(6), fake_config_for_epoch(Epoch(6))), + (Epoch(10), fake_config_for_epoch(Epoch(10))), + ]); + + let configurations = ConfigurationComputerFromMarkers::new(markers); + + assert_eq!(configurations.get_network_configuration(Epoch(4)), None); + } + } } diff --git a/internal/mithril-protocol-config/src/test/double/dummies.rs b/internal/mithril-protocol-config/src/test/double/dummies.rs index 20932af4c4a..a57a7d8727d 100644 --- a/internal/mithril-protocol-config/src/test/double/dummies.rs +++ b/internal/mithril-protocol-config/src/test/double/dummies.rs @@ -1,15 +1,23 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use mithril_common::{ entities::{ - CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, - SignedEntityTypeDiscriminants, + BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, }, test::double::{Dummy, fake_data}, }; -use crate::model::{ - MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration, +use crate::{ + cardano_chain::message::{ + CardanoBlocksTransactionsSigningConfigMessage, CardanoTransactionsSigningConfigMessage, + ProtocolConfigurationForEpochMessage, ProtocolParametersMessage, + }, + model::{ + ConfigurationComputerFromMarkers, MithrilNetworkConfiguration, + MithrilNetworkConfigurationForEpoch, ProtocolConfigurationForEpoch, + SignedEntityTypeConfiguration, + }, }; impl Dummy for MithrilNetworkConfiguration { @@ -42,6 +50,23 @@ impl Dummy for MithrilNetworkConfigurationForEpoch { } } +impl Dummy for ProtocolConfigurationForEpoch { + /// Return a dummy for [ProtocolConfigurationForEpoch] (test-only). + fn dummy() -> Self { + Self { + protocol_parameters: fake_data::protocol_parameters(), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + } + } +} + impl Dummy for SignedEntityTypeConfiguration { /// Return a dummy [SignedEntityTypeConfiguration] (test-only). fn dummy() -> Self { @@ -51,3 +76,72 @@ impl Dummy for SignedEntityTypeConfiguration { } } } + +impl Dummy for ConfigurationComputerFromMarkers { + fn dummy() -> Self { + let conf_a = ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 1, + m: 2, + phi_f: 0.3, + }, + ..Dummy::dummy() + }; + let conf_b = ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 4, + m: 5, + phi_f: 0.6, + }, + ..Dummy::dummy() + }; + let mut markers = BTreeMap::new(); + markers.insert(Epoch(42), conf_a); + markers.insert(Epoch(53), conf_b); + Self { markers } + } +} + +impl Dummy for ProtocolParametersMessage { + fn dummy() -> Self { + Self { + k: 1, + m: 2, + phi_f: 0.3, + } + } +} + +impl Dummy for CardanoTransactionsSigningConfigMessage { + fn dummy() -> Self { + Self { + security_parameter: BlockNumberOffset(10), + step: BlockNumber(5), + } + } +} + +impl Dummy for CardanoBlocksTransactionsSigningConfigMessage { + fn dummy() -> Self { + Self { + security_parameter: BlockNumberOffset(11), + step: BlockNumber(7), + } + } +} + +impl Dummy for ProtocolConfigurationForEpochMessage { + fn dummy() -> Self { + Self { + protocol_parameters: Dummy::dummy(), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(Dummy::dummy()), + cardano_blocks_transactions: Some(Dummy::dummy()), + } + } +} diff --git a/internal/mithril-protocol-config/src/test/double/dummy_markers_reader.rs b/internal/mithril-protocol-config/src/test/double/dummy_markers_reader.rs new file mode 100644 index 00000000000..b29090d23d0 --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/dummy_markers_reader.rs @@ -0,0 +1,68 @@ +use std::sync::RwLock; + +use async_trait::async_trait; +use mithril_common::StdResult; + +use crate::{ + interface::ProtocolConfigurationMarkersReader, model::ConfigurationComputerFromMarkers, +}; + +/// Dummy reader is intended to be used in a test environment (end to end test) +/// to simulate retreiving protocol configurations +#[derive(Default)] +pub struct DummyProtocolConfigurationMarkersReader { + markers: RwLock, +} + +impl DummyProtocolConfigurationMarkersReader { + /// Create a new instance directly from markers + pub fn from_markers(markers: ConfigurationComputerFromMarkers) -> Self { + let myself = Self::default(); + myself.set_markers(markers); + + myself + } + + /// Tells what markers should be sent back by the reader. + pub fn set_markers(&self, markers: ConfigurationComputerFromMarkers) { + let mut my_markers = self.markers.write().unwrap(); + *my_markers = markers; + } +} + +#[async_trait] +impl ProtocolConfigurationMarkersReader for DummyProtocolConfigurationMarkersReader { + async fn read(&self) -> StdResult { + let markers = self.markers.read().unwrap(); + + Ok(markers.clone()) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::test::double::Dummy; + + use super::*; + + #[tokio::test] + async fn empty_dummy_reader() { + let reader = DummyProtocolConfigurationMarkersReader::default(); + + let result = reader.read().await.expect("dummy reader shall not fail reading"); + + assert!(result.markers.is_empty()); + } + + #[tokio::test] + async fn dummy_reader_output() { + let markers = ConfigurationComputerFromMarkers::dummy(); + let reader = DummyProtocolConfigurationMarkersReader::default(); + reader.set_markers(markers.clone()); + + assert_eq!( + markers, + reader.read().await.expect("dummy reader shall not fail reading") + ); + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs index b4dee94f746..3a7406c21fb 100644 --- a/internal/mithril-protocol-config/src/test/double/mod.rs +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -5,5 +5,7 @@ pub mod configuration_provider; mod configuration_provider_with_markers; mod dummies; +mod dummy_markers_reader; pub use configuration_provider_with_markers::*; +pub use dummy_markers_reader::*; diff --git a/mithril-aggregator/src/commands/mod.rs b/mithril-aggregator/src/commands/mod.rs index 66694801ad8..4b666df6d6c 100644 --- a/mithril-aggregator/src/commands/mod.rs +++ b/mithril-aggregator/src/commands/mod.rs @@ -2,6 +2,7 @@ mod config_association; mod database_command; mod era_command; mod genesis_command; +mod protocol_configuration_command; mod serve_command; mod tools_command; @@ -14,6 +15,8 @@ use mithril_doc::{Documenter, GenerateDocCommands, StructDoc}; use slog::{Level, Logger, debug}; use std::{collections::HashMap, path::PathBuf}; +pub use protocol_configuration_command::*; + use crate::{DefaultConfiguration, extract_all}; /// Main command selector @@ -24,6 +27,7 @@ pub enum MainCommand { Serve(serve_command::ServeCommand), Tools(tools_command::ToolsCommand), Database(database_command::DatabaseCommand), + ProtocolConfiguration(protocol_configuration_command::ProtocolConfigurationCommand), #[clap(alias("doc"), hide(true))] GenerateDoc(GenerateDocCommands), } @@ -48,6 +52,7 @@ impl MainCommand { Self::Serve(cmd) => cmd.execute(root_logger, config_builder).await, Self::Tools(cmd) => cmd.execute(root_logger, config_builder).await, Self::Database(cmd) => cmd.execute(root_logger, config_builder).await, + Self::ProtocolConfiguration(cmd) => cmd.execute(root_logger, config_builder).await, Self::GenerateDoc(cmd) => { let commands_configs = Self::extract_config(Self::format_crate_name_to_config_key()); @@ -67,6 +72,8 @@ impl MainCommand { Genesis = { genesis_command::GenesisCommand }, Serve = { serve_command::ServeCommand }, Tools = { tools_command::ToolsCommand }, + ProtocolConfiguration = + { protocol_configuration_command::ProtocolConfigurationCommand }, GenerateDoc = {}, ) } @@ -82,6 +89,7 @@ impl MainCommand { MainCommand::Era(_) => CommandType::CommandLine, MainCommand::Tools(_) => CommandType::CommandLine, MainCommand::Database(_) => CommandType::CommandLine, + MainCommand::ProtocolConfiguration(_) => CommandType::CommandLine, MainCommand::GenerateDoc(_) => CommandType::CommandLine, } } diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs new file mode 100644 index 00000000000..2edbd71a869 --- /dev/null +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -0,0 +1,422 @@ +use anyhow::Context; +use clap::{Parser, Subcommand}; +use config::{ConfigBuilder, Map, Value, builder::DefaultState}; +use serde::{Deserialize, Serialize}; +use slog::{Logger, debug}; +use std::{ + collections::{BTreeSet, HashMap}, + fs::{self, File}, + io::Write, + path::PathBuf, + sync::Arc, +}; +use thiserror::Error; + +use mithril_cardano_node_chain::chain_observer::ChainObserverType; +use mithril_cli_helper::serde_deserialization; +use mithril_common::StdResult; +use mithril_common::crypto_helper::{ + ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSecretKey, +}; +use mithril_common::entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + HexEncodedProtocolConfigurationMarkersSecretKey, ProtocolParameters, + SignedEntityTypeDiscriminants, +}; +use mithril_doc::{Documenter, StructDoc}; + +use crate::{ + ConfigurationSource, ExecutionEnvironment, + configuration::ProtocolConfigurationReaderParameters, extract_all, +}; +use crate::{dependency_injection::DependenciesBuilder, tools::ProtocolConfigurationTools}; + +#[derive(Debug, Error)] +pub enum UserConfImportVerificationError { + #[error("Protocol parameters must be non-zero: {0:?}")] + ZeroValueProtocolParameters(ProtocolParameters), + + #[error("enabled_signed_entity_types contains '{0:?}' without any associed configuration")] + EnabledSignedEntityTypeWithoutConfiguration(SignedEntityTypeDiscriminants), +} + +#[derive(Debug, Clone, Deserialize, Documenter)] +pub struct ProtocolConfigurationParametersConfiguration { + /// Path of the socket opened by the Cardano node + #[example = "`/ipc/node.socket`"] + pub cardano_node_socket_path: PathBuf, + + /// Cardano network + #[example = "`mainnet` or `preprod` or `devnet`"] + network: String, + + /// Cardano chain observer type + pub chain_observer_type: ChainObserverType, + + /// Protocol configuration Reader Adapter Parameters + #[example = "\ + `{ \"address\": \"address\", \"verification_key\": \"key\" }`\ + "] + #[serde(deserialize_with = "serde_deserialization::string_or_struct")] + pub protocol_configuration_reader_adapter_params: ProtocolConfigurationReaderParameters, +} + +impl ConfigurationSource for ProtocolConfigurationParametersConfiguration { + fn environment(&self) -> ExecutionEnvironment { + ExecutionEnvironment::Production + } + + fn cardano_node_socket_path(&self) -> PathBuf { + self.cardano_node_socket_path.clone() + } + + fn network(&self) -> String { + self.network.clone() + } + + fn chain_observer_type(&self) -> ChainObserverType { + self.chain_observer_type.clone() + } + + fn protocol_configuration_reader_parameters(&self) -> ProtocolConfigurationReaderParameters { + self.protocol_configuration_reader_adapter_params.clone() + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct HumanReadableProtocolConfiguration { + pub epoch: Epoch, + pub protocol_parameters: ProtocolParameters, + pub cardano_transaction_signing_config: Option, + pub cardano_blocks_transactions_signing_config: Option, + pub enabled_signed_entity_types: BTreeSet, +} + +impl HumanReadableProtocolConfiguration { + pub fn new( + epoch: Epoch, + protocol_parameters: ProtocolParameters, + cardano_transaction_signing_config: Option, + cardano_blocks_transactions_signing_config: Option, + enabled_signed_entity_types: BTreeSet, + ) -> Self { + HumanReadableProtocolConfiguration { + epoch, + protocol_parameters, + cardano_transaction_signing_config, + cardano_blocks_transactions_signing_config, + enabled_signed_entity_types, + } + } +} + +#[derive(Parser, Debug, Clone)] +pub struct ProtocolConfigurationCommand { + /// commands + #[clap(subcommand)] + pub protocol_configuration_sub_command: ProtocolConfigurationSubCommand, +} + +impl ProtocolConfigurationCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + self.protocol_configuration_sub_command + .execute(root_logger, config_builder) + .await + } + + pub fn extract_config(command_path: String) -> HashMap { + extract_all!( + command_path, + ProtocolConfigurationSubCommand, + ExportMarkers = { ExportProtocolConfigurationSubCommand }, + ImportMarkers = { ImportProtocolConfigurationSubCommand }, + ) + } +} + +#[derive(Debug, Clone, Subcommand)] +pub enum ProtocolConfigurationSubCommand { + /// Protocol configuration export command. + ExportMarkers(ExportProtocolConfigurationSubCommand), + + /// Protocol configuration import command. + ImportMarkers(ImportProtocolConfigurationSubCommand), +} + +impl ProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + match self { + Self::ExportMarkers(cmd) => cmd.execute(root_logger, config_builder).await, + Self::ImportMarkers(cmd) => cmd.execute(root_logger, config_builder).await, + } + } +} + +/// Protocol configuration export command +#[derive(Parser, Debug, Clone)] +pub struct ExportProtocolConfigurationSubCommand { + /// Target path + #[clap(long)] + target_path: PathBuf, +} + +impl ExportProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + Ok(()) + } + + pub fn extract_config(_parent: String) -> HashMap { + HashMap::new() + } +} + +/// Protocol configuration import command +#[derive(Parser, Debug, Clone)] +pub struct ImportProtocolConfigurationSubCommand { + /// Import path of the human readable configurations + #[clap(long, value_parser)] + pub import_path: PathBuf, + + /// target path of the tx datum file + #[clap(long, value_parser)] + pub target_path: PathBuf, + + /// Protocol Configuration Markers Secret Key + #[clap(long, env = "PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY")] + protocol_configuration_markers_secret_key: HexEncodedProtocolConfigurationMarkersSecretKey, +} + +impl ImportProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + // 0 conf & dependencies + let config: ProtocolConfigurationParametersConfiguration = config_builder + .build() + .with_context(|| "configuration build error")? + .try_deserialize() + .with_context(|| "configuration deserialize error")?; + debug!(root_logger, "EXPORT PROTOCOL CONFIGURATION command"; "config" => format!("{config:?}")); + + let mut dependencies_builder = + DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone())); + + let dependencies = dependencies_builder + .create_protocol_configuration_container() + .await + .with_context( + || "Dependencies Builder can not create protocol configuration command dependencies container", + )?; + + //1 - Read the protocol configurations from the file + println!( + "Reading file content {}", + &self.import_path.to_string_lossy() + ); + let json_protocol_configurations = fs::read_to_string(&self.import_path); + + //2 - Parse the json into a protocol configuration list using serde_json + println!("Json parsing ..."); + let protocol_configurations: Vec = + serde_json::from_str(&json_protocol_configurations?)?; + + //3 - Verify protocol configuration consistency, TODO could be move in ProtocolConfigurationTools ? + println!("Verifying protocol configuration consistency..."); + Self::verify_protocol_configurations(&protocol_configurations)?; //return a VerifiedProtocolConfigurations ? + + //3.1 - Verify protocol configuration against on chain configuration + println!("Verifying protocol configuration against on chain configuration..."); + let tools = ProtocolConfigurationTools::from_dependencies(dependencies) + .await + .with_context(|| "protocol-configuration-tools: initialization error")?; + + tools.verify_configurations_against_chain(protocol_configurations.clone())?; + + //4 - Generate Tx datum + println!("Generating Tx datum ..."); + let protocol_configuration_markers_signer = + Self::get_markers_signer(self.protocol_configuration_markers_secret_key.clone())?; + + let tx_datum = tools.generate_tx_datum( + protocol_configurations, + &protocol_configuration_markers_signer, + )?; + + //5 - Verifying datum size + println!("Verifying datum content do not exceed maximum size..."); + tools.verify_tx_datum_size(tx_datum.clone())?; + + //6 - Write datum file + println!("Generating Tx datum output file..."); + let mut target_file = File::create(&self.target_path)?; + target_file.write_all(tx_datum.as_bytes())?; + + println!( + "Sucessfuly write Tx datum file at {}", + &self.target_path.to_string_lossy() + ); + + Ok(()) + } + + fn get_markers_signer( + secret_key: HexEncodedProtocolConfigurationMarkersSecretKey, + ) -> StdResult { + let markers_secret_key = + ProtocolConfigurationMarkersVerifierSecretKey::from_json_hex(&secret_key) + .with_context( + || "json hex decode of protocol configuration markers secret key failure", + )?; + Ok(ProtocolConfigurationMarkersSigner::from_secret_key( + markers_secret_key, + )) + } + + pub fn verify_protocol_configurations( + configurations: &Vec, + ) -> Result<(), UserConfImportVerificationError> { + //TODO verify non zero protocol parameters (other non zero attributes ?) + for config in configurations { + if config.protocol_parameters.k == 0 + || config.protocol_parameters.m == 0 + || config.protocol_parameters.phi_f == 0.0 + { + return Err( + UserConfImportVerificationError::ZeroValueProtocolParameters( + config.protocol_parameters.clone(), + ), + ); + } + if config + .enabled_signed_entity_types + .contains(&SignedEntityTypeDiscriminants::CardanoTransactions) + && config.cardano_transaction_signing_config.is_none() + { + return Err( + UserConfImportVerificationError::EnabledSignedEntityTypeWithoutConfiguration( + SignedEntityTypeDiscriminants::CardanoTransactions, + ), + ); + } + if config + .enabled_signed_entity_types + .contains(&SignedEntityTypeDiscriminants::CardanoBlocksTransactions) + && config.cardano_blocks_transactions_signing_config.is_none() + { + return Err( + UserConfImportVerificationError::EnabledSignedEntityTypeWithoutConfiguration( + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + ), + ); + } + } + + Ok(()) + } + + pub fn extract_config(_parent: String) -> HashMap { + HashMap::new() + } +} + +#[cfg(test)] +mod tests { + use mithril_common::{entities::ProtocolParameters, test::double::Dummy}; + + use super::*; + + mod verify_protocol_configurations { + + use super::*; + + #[test] + fn should_throw_error_with_protocol_parameter_at_zero() { + let protocol_parameters_list_to_test = [ + ProtocolParameters::new(0, 1, 0.123), + ProtocolParameters::new(1, 0, 0.123), + ProtocolParameters::new(1, 1, 0.0), + ]; + + for protocol_parameters in protocol_parameters_list_to_test { + let configurations = vec![HumanReadableProtocolConfiguration { + protocol_parameters: protocol_parameters.clone(), + ..Dummy::dummy() + }]; + + ImportProtocolConfigurationSubCommand::verify_protocol_configurations( + &configurations, + ) + .expect_err( + &format!( + "Protocol parameters must be non-zero: {:?}", + protocol_parameters + ) + .to_string(), + ); + } + } + + #[test] + fn shoud_throw_error_if_enabled_entity_types_contains_cardano_transactions_without_configuration() + { + let configurations = vec![HumanReadableProtocolConfiguration { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: None, + ..Dummy::dummy() + }]; + + ImportProtocolConfigurationSubCommand::verify_protocol_configurations(&configurations) + .expect_err("enabled_signed_entity_types contains 'CardanoTransactions' without any associed configuration"); + } + + #[test] + fn shoud_throw_error_if_enabled_entity_types_contains_cardano_blocks_transactions_without_configuration() + { + let configurations = vec![HumanReadableProtocolConfiguration { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + ]), + cardano_blocks_transactions_signing_config: None, + ..Dummy::dummy() + }]; + + ImportProtocolConfigurationSubCommand::verify_protocol_configurations(&configurations) + .expect_err("enabled_signed_entity_types contains 'CardanoBlocksTransactions' without any associed configuration"); + } + } + + #[test] + fn import_subcommand_parses_flag() { + let signer_secret_key = ProtocolConfigurationMarkersSigner::create_deterministic_signer() + .secret_key() + .to_json_hex() + .expect("create_deterministic_signer for secret key should not fail"); + + ImportProtocolConfigurationSubCommand::try_parse_from([ + "import-markers", + "--import-path", + "tests/human_readable_protocol_configuration_toto.json", + "--target-path", + "/tests/protocol_configuration_tx_datum", + "--protocol-configuration-markers-secret-key", + &signer_secret_key, + ]) + .expect("CLI parse should succeed"); + } +} diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index 167508be0f9..200c42f0a99 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -9,7 +9,9 @@ use serde::Deserialize; use mithril_cardano_node_chain::chain_observer::ChainObserverType; use mithril_cli_helper::{register_config_value, serde_deserialization}; -use mithril_common::crypto_helper::{GenesisEd25519Signer, ManifestSigner}; +use mithril_common::crypto_helper::{ + GenesisEd25519Signer, ManifestSigner, ProtocolConfigurationMarkersVerifierVerificationKey, +}; use mithril_common::entities::{ BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, CompressionAlgorithm, ConfigSecret, @@ -196,6 +198,11 @@ pub trait ConfigurationSource { panic!("era_reader_adapter_params is not implemented."); } + /// Protocol configuration reader adapter parameters + fn protocol_configuration_reader_parameters(&self) -> ProtocolConfigurationReaderParameters { + panic!("protocol_configuration_reader_parameters is not implemented."); + } + /// Configuration of the ancillary files signer /// /// **IMPORTANT**: The cryptographic scheme used is ED25519 @@ -772,6 +779,24 @@ impl FromStr for AncillaryFilesSignerConfig { } } +/// Configuration of the protocol Configuration Reader +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct ProtocolConfigurationReaderParameters { + /// Address + pub address: String, + + /// Verification key + pub verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, +} + +impl FromStr for ProtocolConfigurationReaderParameters { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + impl ServeCommandConfiguration { /// Create a sample configuration mainly for tests pub fn new_sample(tmp_path: PathBuf) -> Self { diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index b20a56844b9..8475c04c714 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -34,7 +34,9 @@ use mithril_common::{ }; use mithril_era::{EraChecker, EraReader, EraReaderAdapter}; use mithril_persistence::sqlite::{SqliteConnection, SqliteConnectionPool}; -use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; +use mithril_protocol_config::interface::{ + MithrilNetworkConfigurationProvider, ProtocolConfigurationMarkersReader, +}; use mithril_signed_entity_lock::SignedEntityTypeLock; use mithril_ticker::TickerService; @@ -49,6 +51,7 @@ use crate::{ AggregatorCardanoChainDataRepository, CertificateRepository, EpochSettingsStore, OpenMessageRepository, SignedEntityStorer, SignerStore, StakePoolStore, }, + dependency_injection::ProtocolConfigurationCommandDependenciesContainer, event_store::{EventMessage, TransmitterService}, file_uploaders::FileUploader, http_server::routes::router::{self, RouterConfig, RouterState}, @@ -221,6 +224,9 @@ pub struct DependenciesBuilder { /// Era reader service pub era_reader: Option>, + ///Protocol configuration reader service + pub protocol_configuration_reader: Option>, + /// Event Transmitter Service pub event_transmitter: Option>>, @@ -337,6 +343,7 @@ impl DependenciesBuilder { era_reader_adapter: None, era_checker: None, era_reader: None, + protocol_configuration_reader: None, event_transmitter: None, event_transmitter_channel: (None, None), api_version_provider: None, @@ -398,6 +405,7 @@ impl DependenciesBuilder { signer_registration_round_opener: self.get_signer_registration_round_opener().await?, era_checker: self.get_era_checker().await?, era_reader: self.get_era_reader().await?, + protocol_configuration_reader: self.get_protocol_configuration_reader().await?, event_transmitter: self.get_event_transmitter().await?, api_version_provider: self.get_api_version_provider().await?, stake_distribution_service: self.get_stake_distribution_service().await?, @@ -497,6 +505,23 @@ impl DependenciesBuilder { Ok(dependencies) } + /// Create dependencies for protocol configuration commands + pub async fn create_protocol_configuration_container( + &mut self, + ) -> Result { + let network = self.configuration.get_network().with_context( + || "Dependencies Builder can not get Cardano network while building protocol configuration container", + )?; + let dependencies = ProtocolConfigurationCommandDependenciesContainer { + network, + chain_observer: self.get_chain_observer().await?, + protocol_configuration_reader: self.get_protocol_configuration_reader().await?, + logger: self.root_logger(), + }; + + Ok(dependencies) + } + /// Create dependencies for database command pub async fn create_database_command_container( &mut self, diff --git a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs index 9c25a9ae75b..b724306fce5 100644 --- a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs +++ b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs @@ -2,8 +2,13 @@ use std::sync::Arc; use mithril_common::api_version::APIVersionProvider; use mithril_common::entities::{Epoch, SupportedEra}; +use mithril_common::test::double::Dummy; use mithril_era::adapters::{EraReaderAdapterBuilder, EraReaderDummyAdapter}; use mithril_era::{EraChecker, EraMarker, EraReader, EraReaderAdapter}; +use mithril_protocol_config::cardano_chain::protocol_configuration_reader::CardanoChainProtocolConfigurationMarkersReader; +use mithril_protocol_config::interface::ProtocolConfigurationMarkersReader; +use mithril_protocol_config::model::ConfigurationComputerFromMarkers; +use mithril_protocol_config::test::double::DummyProtocolConfigurationMarkersReader; use crate::ExecutionEnvironment; use crate::dependency_injection::{DependenciesBuilder, DependenciesBuilderError, Result}; @@ -82,4 +87,32 @@ impl DependenciesBuilder { pub async fn get_era_checker(&mut self) -> Result> { get_dependency!(self.era_checker) } + + async fn build_protocol_configuration_reader( + &mut self, + ) -> Result> { + let protocol_configuration_markers_reader: Arc = + match self.configuration.environment() { + ExecutionEnvironment::Production => { + let parameters = self.configuration.protocol_configuration_reader_parameters(); + Arc::new(CardanoChainProtocolConfigurationMarkersReader::new( + parameters.address, + self.get_chain_observer().await?, + parameters.verification_key, + )) + } + _ => Arc::new(DummyProtocolConfigurationMarkersReader::from_markers( + ConfigurationComputerFromMarkers::dummy(), + )), + }; + + Ok(protocol_configuration_markers_reader) + } + + /// [ProtocolConfigurationReader] service + pub async fn get_protocol_configuration_reader( + &mut self, + ) -> Result> { + get_dependency!(self.protocol_configuration_reader) + } } diff --git a/mithril-aggregator/src/dependency_injection/containers/mod.rs b/mithril-aggregator/src/dependency_injection/containers/mod.rs index e8109eec3a4..e64b8b7ab07 100644 --- a/mithril-aggregator/src/dependency_injection/containers/mod.rs +++ b/mithril-aggregator/src/dependency_injection/containers/mod.rs @@ -1,7 +1,9 @@ mod genesis; +mod protocol_configuration; mod serve; pub use genesis::GenesisCommandDependenciesContainer; +pub use protocol_configuration::ProtocolConfigurationCommandDependenciesContainer; pub use serve::*; use std::sync::Arc; diff --git a/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs b/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs new file mode 100644 index 00000000000..8412314ea86 --- /dev/null +++ b/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use mithril_protocol_config::interface::ProtocolConfigurationMarkersReader; +use slog::Logger; + +use mithril_cardano_node_chain::chain_observer::ChainObserver; +use mithril_common::CardanoNetwork; + +/// Dependencies container for the protocol configuration commands +pub struct ProtocolConfigurationCommandDependenciesContainer { + /// Cardano network + pub network: CardanoNetwork, + + /// Chain observer + pub chain_observer: Arc, + + /// Protocol configuration reader + pub protocol_configuration_reader: Arc, + + /// Logger. + pub logger: Logger, +} diff --git a/mithril-aggregator/src/dependency_injection/containers/serve.rs b/mithril-aggregator/src/dependency_injection/containers/serve.rs index 98511d0b744..7f19a2e13a7 100644 --- a/mithril-aggregator/src/dependency_injection/containers/serve.rs +++ b/mithril-aggregator/src/dependency_injection/containers/serve.rs @@ -1,3 +1,4 @@ +use mithril_protocol_config::interface::ProtocolConfigurationMarkersReader; use slog::Logger; use std::sync::Arc; use tokio::sync::RwLock; @@ -68,6 +69,9 @@ pub struct ServeCommandDependenciesContainer { /// Era reader service pub(crate) era_reader: Arc, + /// Protocol configuration service + pub(crate) protocol_configuration_reader: Arc, + /// Event Transmitter Service pub(crate) event_transmitter: Arc>, diff --git a/mithril-aggregator/src/test/double/dummies.rs b/mithril-aggregator/src/test/double/dummies.rs index 73161f6c45e..2ca6384579d 100644 --- a/mithril-aggregator/src/test/double/dummies.rs +++ b/mithril-aggregator/src/test/double/dummies.rs @@ -1,7 +1,17 @@ +use std::collections::BTreeSet; + use chrono::Utc; use uuid::Uuid; -use mithril_common::test::double::{Dummy, fake_data}; +use mithril_common::{ + entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + ProtocolParameters, SignedEntityTypeDiscriminants, + }, + test::double::{Dummy, fake_data}, +}; + +use crate::commands::HumanReadableProtocolConfiguration; mod record { use mithril_common::entities::{ProtocolMessage, SignedEntityType}; @@ -101,3 +111,20 @@ mod entities { } } } + +impl Dummy for HumanReadableProtocolConfiguration { + fn dummy() -> Self { + HumanReadableProtocolConfiguration::new( + Epoch(42), + ProtocolParameters::new(10, 20, 0.123), + Some(CardanoTransactionsSigningConfig::dummy()), + Some(CardanoBlocksTransactionsSigningConfig::dummy()), + BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + ]), + ) + } +} diff --git a/mithril-aggregator/src/tools/mod.rs b/mithril-aggregator/src/tools/mod.rs index 71ebc698ffd..eb363796d0c 100644 --- a/mithril-aggregator/src/tools/mod.rs +++ b/mithril-aggregator/src/tools/mod.rs @@ -3,6 +3,7 @@ mod era; pub mod file_archiver; pub mod file_size; mod genesis; +mod protocol_configuration; pub mod signer_importer; mod single_signature_authenticator; pub mod url_sanitizer; @@ -13,6 +14,7 @@ pub use era::EraTools; #[cfg(feature = "future_snark")] pub use genesis::GenesisSignedPayload; pub use genesis::GenesisTools; +pub use protocol_configuration::ProtocolConfigurationTools; pub use single_signature_authenticator::*; pub use vacuum_tracker::VacuumTracker; diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs new file mode 100644 index 00000000000..676ecaea280 --- /dev/null +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -0,0 +1,607 @@ +use std::collections::BTreeMap; + +use anyhow::Context; +use mithril_cardano_node_chain::entities::{TxDatumBuilder, TxDatumFieldValue}; +use mithril_common::{ + StdResult, crypto_helper::ProtocolConfigurationMarkersSigner, entities::Epoch, +}; + +use mithril_protocol_config::{ + cardano_chain::{ + ProtocolConfigurationMarkersPayloadCardanoChain, + message::{ProtocolConfigurationForEpochMessage, ProtocolConfigurationMarker}, + }, + model::{ConfigurationComputerFromMarkers, ProtocolConfigurationForEpoch}, +}; +use slog::{Logger, info, warn}; +use thiserror::Error; + +use crate::{ + commands::HumanReadableProtocolConfiguration, + dependency_injection::ProtocolConfigurationCommandDependenciesContainer, +}; + +const EPOCH_OFFSET: u64 = 3; +const DATUM_MAX_SIZE_KB: usize = 10; + +#[derive(Debug, Error)] +pub enum ProtocolConfigurationVerifierError { + #[error("Configuration to import for {0:?}, is not the same has configuration on chain")] + NotSameConfigurationForEpoch(Epoch), + + #[error("Size of datum is {0:?} KB (Maximum authorized size is {DATUM_MAX_SIZE_KB} KB")] + DatumMaxSizeExceeded(f64), +} + +type ProtocolConfigurationToolsResult = StdResult; + +/// Configuration for the protocol configuration tools. +pub struct ProtocolConfigurationToolsConfiguration { + /// Current epoch. + pub epoch: Epoch, + + //On chain configurations by Epoch. + pub on_chain_configurations: ConfigurationComputerFromMarkers, +} + +pub struct ProtocolConfigurationTools { + configuration: ProtocolConfigurationToolsConfiguration, + + logger: Logger, +} + +impl ProtocolConfigurationTools { + pub fn new(configuration: ProtocolConfigurationToolsConfiguration, logger: Logger) -> Self { + Self { + configuration, + logger, + } + } + + pub async fn from_dependencies( + dependencies: ProtocolConfigurationCommandDependenciesContainer, + ) -> StdResult { + let epoch = dependencies + .chain_observer + .get_current_epoch() + .await? + .with_context(|| "Chain observer can not retrieve current epoch")?; + + let on_chain_configurations = dependencies.protocol_configuration_reader.read().await?; + + let configuration = ProtocolConfigurationToolsConfiguration { + epoch, + on_chain_configurations, + }; + + Ok(Self::new(configuration, dependencies.logger)) + } + + /// Verify if configuration to import share same windows as on chain configuration for current epoch + pub fn verify_configurations_against_chain( + &self, + configurations_to_import: Vec, + ) -> Result<(), ProtocolConfigurationVerifierError> { + let current_epoch = self.configuration.epoch; + info!(&self.logger, "Current epoch is {}", current_epoch); + + let markers_from_chain = self.configuration.on_chain_configurations.clone(); + let markers_to_import = to_configuration_computer_from_markers(configurations_to_import); + + let epoch_range_to_verify = (current_epoch.0 - EPOCH_OFFSET)..=current_epoch.0; + info!( + &self.logger, + "Verifying configurations for epoch range [{:?}]", epoch_range_to_verify + ); + + for epoch in epoch_range_to_verify.map(Epoch) { + let marker_to_import = markers_to_import.get_network_configuration(epoch); + let marker_on_chain = markers_from_chain.get_network_configuration(epoch); + if marker_on_chain.is_some() { + if marker_to_import != marker_on_chain { + return Err( + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(epoch), + ); + } + } else { + warn!( + &self.logger, + "No configuration found on chain for {}, this is normal behavior if there is no protocol configuration marker on chain", + epoch + ); + } + } + Ok(()) + } + + /// Generate TxDatum from HumanReadableProtocolConfiguration Vec + pub fn generate_tx_datum( + &self, + configurations: Vec, + protocol_configuration_markers_signer: &ProtocolConfigurationMarkersSigner, + ) -> ProtocolConfigurationToolsResult { + let mut markers: Vec = Vec::new(); + for configuration in configurations { + let protocol_configuration_for_epoch: ProtocolConfigurationForEpochMessage = + configuration.clone().into(); + let marker: ProtocolConfigurationMarker = ProtocolConfigurationMarker::new( + configuration.epoch, + protocol_configuration_for_epoch.to_cbor_hex()?, + ); + markers.push(marker); + } + let signed_markers_payload = ProtocolConfigurationMarkersPayloadCardanoChain::new(markers) + .sign(protocol_configuration_markers_signer)?; + + let tx_datum = TxDatumBuilder::new() + .add_field(TxDatumFieldValue::Bytes( + signed_markers_payload.to_json_hex()?, + )) + .build()?; + Ok(tx_datum.0) + } + + pub fn verify_tx_datum_size( + &self, + datum: String, + ) -> Result<(), ProtocolConfigurationVerifierError> { + let size_bytes = datum.len(); + let size_kb = size_bytes as f64 / 1024.0; + + println!("Datum size: {:.2} KB", size_kb); + + if size_bytes > DATUM_MAX_SIZE_KB * 1024 { + return Err(ProtocolConfigurationVerifierError::DatumMaxSizeExceeded( + size_kb, + )); + } + + Ok(()) + } +} + +impl From for ProtocolConfigurationForEpochMessage { + fn from(config: HumanReadableProtocolConfiguration) -> Self { + ProtocolConfigurationForEpochMessage { + protocol_parameters: config.protocol_parameters.into(), + enabled_signed_entity_types: config.enabled_signed_entity_types, + cardano_transactions: config.cardano_transaction_signing_config.map(Into::into), + cardano_blocks_transactions: config + .cardano_blocks_transactions_signing_config + .map(Into::into), + } + } +} + +impl From for ProtocolConfigurationForEpoch { + fn from(config: HumanReadableProtocolConfiguration) -> Self { + ProtocolConfigurationForEpoch { + protocol_parameters: config.protocol_parameters, + enabled_signed_entity_types: config.enabled_signed_entity_types, + cardano_transactions: config.cardano_transaction_signing_config, + cardano_blocks_transactions: config.cardano_blocks_transactions_signing_config, + } + } +} + +fn to_configuration_computer_from_markers( + configs: Vec, +) -> ConfigurationComputerFromMarkers { + let mut markers = BTreeMap::new(); + + for config in configs { + markers.insert(config.epoch, ProtocolConfigurationForEpoch::from(config)); + } + ConfigurationComputerFromMarkers::new(markers) +} + +#[cfg(test)] +mod tests { + use mithril_common::{ + entities::{ + BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, + SignedEntityTypeDiscriminants, + }, + test::double::Dummy, + }; + use std::collections::{BTreeMap, BTreeSet}; + + use crate::test::TestLogger; + + use super::*; + + fn build_tools_dummy() -> ProtocolConfigurationTools { + let configuration = ProtocolConfigurationToolsConfiguration { + epoch: Epoch(30), + on_chain_configurations: ConfigurationComputerFromMarkers::new(BTreeMap::new()), + }; + ProtocolConfigurationTools::new(configuration, TestLogger::stdout()) + } + + fn build_tools( + current_epoch: Epoch, + on_chain_configurations: ConfigurationComputerFromMarkers, + logger: Logger, + ) -> ProtocolConfigurationTools { + let configuration = ProtocolConfigurationToolsConfiguration { + epoch: current_epoch, + on_chain_configurations, + }; + ProtocolConfigurationTools::new(configuration, logger) + } + + #[test] + fn test_from_human_readable_protocol_configuration() { + let human_readable_conf = HumanReadableProtocolConfiguration { + epoch: Epoch(42), + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions_signing_config: Some( + CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(150), + step: BlockNumber(20), + }, + ), + }; + + let expected_protocol_configuration_for_epoch = ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(150), + step: BlockNumber(20), + }), + }; + + assert_eq!( + ProtocolConfigurationForEpoch::from(human_readable_conf), + expected_protocol_configuration_for_epoch + ); + } + + #[test] + fn generate_tx_datum_ok() { + let configurations = vec![HumanReadableProtocolConfiguration { + epoch: Epoch(42), + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions_signing_config: None, + }]; + let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); + let tools = build_tools_dummy(); + assert!(tools.generate_tx_datum(configurations, &signer).is_ok()); + } + + #[test] + fn verify_tx_datum_size_is_ok_with_datum_under_10_kb() { + let tools = build_tools_dummy(); + assert!(tools.verify_tx_datum_size("tx datum under 10 kb".to_string()).is_ok()); + } + + #[test] + fn verify_tx_datum_size_is_ok_with_datum_from_dummy_configuration() { + let configurations = vec![ + HumanReadableProtocolConfiguration { + epoch: Epoch(42), + ..Dummy::dummy() + }, + HumanReadableProtocolConfiguration { + epoch: Epoch(53), + ..Dummy::dummy() + }, + ]; + let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); + let tools = build_tools_dummy(); + let datum = tools + .generate_tx_datum(configurations, &signer) + .expect("generate_tx_datum should not fail"); + + assert!(tools.verify_tx_datum_size(datum).is_ok()); + } + + mod verify_configurations_against_chain { + use super::*; + + /// instanciate a unique ProtocolConfigurationForEpoch based on char + fn fake_configuration(conf: char) -> ProtocolConfigurationForEpoch { + ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: conf as u64, + m: conf as u64, + phi_f: 1.2, + }, + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + } + } + + /// Instanciate a HumanReadableProtocolConfiguration at epoch with a unique char configuration + fn fake_configuration_to_import( + epoch: Epoch, + conf: char, + ) -> HumanReadableProtocolConfiguration { + HumanReadableProtocolConfiguration { + epoch, + protocol_parameters: ProtocolParameters { + k: conf as u64, + m: conf as u64, + phi_f: 1.2, + }, + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions_signing_config: Some( + CardanoBlocksTransactionsSigningConfig::dummy(), + ), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + } + } + + fn build_on_chain_markers( + configurations: Vec<(Epoch, char)>, + ) -> ConfigurationComputerFromMarkers { + let mut on_chain_markers = BTreeMap::new(); + for conf in configurations { + on_chain_markers.insert(conf.0, fake_configuration(conf.1)); + } + ConfigurationComputerFromMarkers::new(on_chain_markers) + } + + fn build_configurations_to_import( + configurations: Vec<(Epoch, char)>, + ) -> Vec { + configurations + .iter() + .map(|conf| fake_configuration_to_import(conf.0, conf.1)) + .collect() + } + + #[test] + fn ok_with_only_one_same_epoch_conf_in_offset_window() { + let (logger, log_inspector) = TestLogger::memory(); + + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(38), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(44), fake_configuration('B')); //conf inside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = + build_configurations_to_import(vec![(Epoch(44), 'B'), (Epoch(56), 'Z')]); + + let tools = build_tools(current_epoch, on_chain_configurations, logger); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + + assert!( + log_inspector.contains_log("Verifying configurations for epoch range [44..=47]") + ) + } + + #[test] + fn ok_with_only_one_same_epoch_conf_outside_offset_window_with_fallback() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(38), 'B'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn ok_with_only_one_same_conf_at_different_epoch() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(40), 'B'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn ko_because_last_known_on_chain_configuration_b_for_offset_window_is_not_repeated() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(40), 'C'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + let result = tools.verify_configurations_against_chain(configurations_to_import); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(44)) + )); + } + + #[test] + fn full_offset_window_have_to_be_repeated_if_it_have_different_configuration() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(43), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(44), fake_configuration('B')); //conf inside offset window + on_chain_markers.insert(Epoch(45), fake_configuration('C')); //conf inside offset window + on_chain_markers.insert(Epoch(46), fake_configuration('D')); //conf inside offset window + on_chain_markers.insert(Epoch(47), fake_configuration('E')); //conf inside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(44), 'B'), + fake_configuration_to_import(Epoch(45), 'C'), + fake_configuration_to_import(Epoch(46), 'D'), + fake_configuration_to_import(Epoch(47), 'E'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + + //It fail if one of epoch/conf from offset window is not repeated + let bad_configurations_to_import = vec![ + fake_configuration_to_import(Epoch(44), 'B'), + fake_configuration_to_import(Epoch(45), 'X'), + fake_configuration_to_import(Epoch(46), 'D'), + fake_configuration_to_import(Epoch(47), 'E'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + let result = tools.verify_configurations_against_chain(bad_configurations_to_import); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(45)) + )); + } + + #[test] + fn window_to_repeat_dont_have_to_be_exactly_at_same_epoch_as_long_as_it_can_fallback_to_same_configuration() + { + let current_epoch = Epoch(47); + let on_chain_configurations = + build_on_chain_markers(vec![(Epoch(30), 'A'), (Epoch(44), 'B'), (Epoch(47), 'B')]); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(32), 'A'), + fake_configuration_to_import(Epoch(40), 'B'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn verification_with_no_markers_on_chain_should_be_ok() { + let current_epoch = Epoch(47); + let on_chain_configurations = ConfigurationComputerFromMarkers::new(BTreeMap::new()); + + let configurations_to_import = vec![fake_configuration_to_import(Epoch(53), 'Z')]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn verification_with_no_markers_to_import_should_be_ko() { + let current_epoch = Epoch(47); + let on_chain_configurations = + build_on_chain_markers(vec![(Epoch(30), 'A'), (Epoch(44), 'B')]); + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + let result = tools.verify_configurations_against_chain(vec![]); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(44)) + )); + } + } +} diff --git a/mithril-aggregator/tests/human_readable_protocol_configuration.json b/mithril-aggregator/tests/human_readable_protocol_configuration.json new file mode 100644 index 00000000000..f51a88685b8 --- /dev/null +++ b/mithril-aggregator/tests/human_readable_protocol_configuration.json @@ -0,0 +1,43 @@ +[ + { + "epoch": 42, + "protocol_parameters": { + "k": 9, + "m": 77, + "phi_f": 0.5 + }, + "cardano_transactions_signing_config": { + "security_parameter": 100, + "step": 10 + }, + "enabled_signed_entity_types": + [ + "MithrilStakeDistribution", + "CardanoDatabase", + "CardanoTransactions" + ] + }, + { + "epoch": 43, + "protocol_parameters": { + "k": 10, + "m": 88, + "phi_f": 0.7 + }, + "cardano_transactions_signing_config": { + "security_parameter": 100, + "step": 10 + }, + "cardano_blocks_transactions_signing_config": { + "security_parameter": 150, + "step": 20 + }, + "enabled_signed_entity_types": + [ + "MithrilStakeDistribution", + "CardanoDatabase", + "CardanoTransactions", + "CardanoBlocksTransactions" + ] + } +] \ No newline at end of file diff --git a/mithril-common/src/crypto_helper/ed25519_alias.rs b/mithril-common/src/crypto_helper/ed25519_alias.rs index 73daf34b3c0..9d3694a3258 100644 --- a/mithril-common/src/crypto_helper/ed25519_alias.rs +++ b/mithril-common/src/crypto_helper/ed25519_alias.rs @@ -15,6 +15,15 @@ pub mod era { pub type EraMarkersVerifier = Ed25519Verifier; /// [EraMarkersSigner] and [EraMarkersVerifier] related errors. pub type EraMarkersVerifierError = Ed25519VerifierError; + + /// Protocol Configuration markers verifier secret key + pub type ProtocolConfigurationMarkersVerifierSecretKey = Ed25519SecretKey; + /// Protocol Configuration markers verifier verification key + pub type ProtocolConfigurationMarkersVerifierVerificationKey = Ed25519VerificationKey; + /// Protocol Configuration markers verifier signature + pub type ProtocolConfigurationMarkersVerifierSignature = Ed25519Signature; + /// A cryptographic signer that is responsible for signing the ProtocolConfigurationMarkers + pub type ProtocolConfigurationMarkersSigner = Ed25519Signer; } pub mod manifest { diff --git a/mithril-common/src/entities/type_alias.rs b/mithril-common/src/entities/type_alias.rs index 2c4b7b59610..80f211b2fe1 100644 --- a/mithril-common/src/entities/type_alias.rs +++ b/mithril-common/src/entities/type_alias.rs @@ -63,6 +63,9 @@ pub type HexEncodedDigest = HexEncodedKey; /// Hex encoded Era Markers Secret Key pub type HexEncodedEraMarkersSecretKey = HexEncodedKey; +/// Hex encoded Protocol Configuration Markers Secret Key +pub type HexEncodedProtocolConfigurationMarkersSecretKey = HexEncodedKey; + /// Number of SPOs pub type TotalSPOs = u32;