From d243b486689f79f934f612ce3ff22051f7ef9435 Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Fri, 19 Jan 2024 10:00:19 +0100 Subject: [PATCH 01/25] Update current docs By running 'make update-current' --- .../nodes/mithril-aggregator.md | 15 ++- .../nodes/mithril-client-library-wasm.md | 125 ++++++++++++++++++ .../nodes/mithril-client-library.md | 20 ++- .../developer-docs/nodes/mithril-client.md | 21 ++- .../getting-started/bootstrap-cardano-node.md | 2 +- .../manual/getting-started/run-signer-node.md | 2 +- 6 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md diff --git a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-aggregator.md b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-aggregator.md index 5d1a87e4590..0a4fb1c0273 100644 --- a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-aggregator.md +++ b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-aggregator.md @@ -29,9 +29,9 @@ Mithril aggregator is responsible for collecting individual signatures from the ## Resources -| Node | Source repository | Rust documentation | Docker packages | REST API | -|:-:|:-----------------:|:------------------:|:---------------:|:---------------:| -**Mithril aggregator** | [:arrow_upper_right:](https://github.com/input-output-hk/mithril/tree/main/mithril-aggregator) | [:arrow_upper_right:](https://mithril.network/rust-doc/mithril_aggregator/index.html) | [:arrow_upper_right:](https://github.com/input-output-hk/mithril/pkgs/container/mithril-aggregator) | [:arrow_upper_right:](/doc/aggregator-api) | +| Node | Source repository | Rust documentation | Docker packages | REST API +|:-:|:-----------------:|:------------------:|:---------------:| +**Mithril aggregator** | [:arrow_upper_right:](https://github.com/input-output-hk/mithril/tree/main/mithril-aggregator) | [:arrow_upper_right:](https://mithril.network/rust-doc/mithril_aggregator/index.html) | [:arrow_upper_right:](https://github.com/input-output-hk/mithril/pkgs/container/mithril-aggregator) | [:arrow_upper_right:](/doc/aggregator-api) ## Pre-requisites @@ -300,7 +300,7 @@ You can run 'era generate-tx-datum' to create the transaction datum file that wi **Case 1**: If there is only one supported era in the code, create the datum file: ```bash -./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** +./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** --target-path **TARGET_PATH** ``` You should see something like: @@ -312,13 +312,13 @@ You should see something like: **Case 2**: If there are two supported eras in the code and the activation epoch of the upcoming era is not yet known, run the command: ```bash -./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** +./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** --target-path **TARGET_PATH** ``` **Case 3**: If there are two supported eras in the code and the activation epoch of the era switch is known to be at the following epoch, run the command: ```bash -./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --next-era-epoch **EPOCH_AT_WHICH_NEXT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** +./mithril-aggregator era generate-tx-datum --current-era-epoch **EPOCH_AT_WHICH_CURRENT_ERA_STARTS** --next-era-epoch **EPOCH_AT_WHICH_NEXT_ERA_STARTS** --era-markers-secret-key **YOUR_ERA_ACTIVATION_SECRET_KEY** --target-path **TARGET_PATH** ``` ## Release the build and run the binary 'tools' command @@ -439,8 +439,10 @@ Here is a list of the available parameters: | `snapshot_bucket_name` | - | - | `SNAPSHOT_BUCKET_NAME` | Name of the bucket where the snapshots are stored | - | `snapshot-bucket` | :heavy_check_mark: | Required if `snapshot_uploader_type` is `gcp` | `snapshot_use_cdn_domain` | - | - | `SNAPSHOT_USE_CDN_DOMAIN` | Use CDN domain for constructing snapshot url | `false` | - | - | To be used if `snapshot_uploader_type` is `gcp` | `run_interval` | - | - | `RUN_INTERVAL` | Interval between two runtime cycles in ms | - | `60000` | :heavy_check_mark: | +| `chain_observer_type` | `--chain-observer-type` | - | `CHAIN_OBSERVER_TYPE` | Chain observer type that can be `cardano-cli`, `pallas` or `fake`. | `pallas` | - | - | | `era_reader_adapter_type` | `--era-reader-adapter-type` | - | `ERA_READER_ADAPTER_TYPE` | Era reader adapter type that can be `cardano-chain`, `file` or `bootstrap`. | `bootstrap` | - | - | | `era_reader_adapter_params` | `--era-reader-adapter-params` | - | `ERA_READER_ADAPTER_PARAMS` | Era reader adapter params that is an optional JSON encoded parameters structure that is expected depending on the `era_reader_adapter_type` parameter | - | - | - | +| `signed_entity_types` | `--signed-entity-types` | - | `SIGNED_ENTITY_TYPES` | Signed entity types parameters (discriminants names in an ordered comma separated list) | - | `MithrilStakeDistribution,CardanoImmutableFilesFull,CardanoStakeDistribution` | - | | `snapshot_compression_algorithm` | `--snapshot-compression-algorithm` | - | `SNAPSHOT_COMPRESSION_ALGORITHM` | Compression algorithm of the snapshot archive | `zstandard` | `gzip` or `zstandard` | - | | `zstandard_parameters` | - | - | `ZSTANDARD_PARAMETERS__LEVEL` and `ZSTANDARD_PARAMETERS__NUMBER_OF_WORKERS` | Zstandard specific parameters | - | `{ level: 9, number_of_workers: 4 }` | - | @@ -483,5 +485,6 @@ Here is a list of the available parameters: | `current_era_epoch` | `--current-era-epoch` | - | `CURRENT_ERA_EPOCH` | Epoch at which current era starts. | - | - | - | :heavy_check_mark: | | `next_era_epoch` | `--next-era-epoch` | - | `NEXT_ERA_EPOCH` | Epoch at which the next era starts. If not specified and an upcoming era is available, it will announce the next era. If specified, it must be strictly greater than `current-epoch-era` | - | - | - | - | | `era_markers_secret_key` | `--era-markers-secret-key` | - | `ERA_MARKERS_SECRET_KEY` | Era markers secret key that is used to verify the authenticity of the era markers on the chain. | - | - | - | :heavy_check_mark: | +| `target_path` | `--target-path` | - | - | Path of the file to export the payload to. | - | - | - | - | The `tools recompute-certificates-hash` command has no dedicated parameters. diff --git a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md new file mode 100644 index 00000000000..fa43727e1ca --- /dev/null +++ b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md @@ -0,0 +1,125 @@ +--- +sidebar_position: 5 +--- + +import NetworksMatrix from '../../../networks-matrix.md'; +import CompiledBinaries from '../../../compiled-binaries.md' + +# Mithril client library WASM + +:::info + +Mithril client library WASM can be used by Javascript developers to use the Mithril network in their web applications. + +It is responsible for handling the different types of data certified by Mithril, and available through a Mithril aggregator: +- [**Snapshot**](../../../glossary.md#snapshot): list and get. +- [**Mithril stake distribution**](../../../glossary.md#stake-distribution): list and get. +- [**Certificate**](../../../glossary.md#certificate): list, get, and chain validation. + +::: + +:::tip + +* For more information about the **Mithril network**, please see the [architecture](../../../mithril/mithril-network/architecture.md) overview. + +::: + +:::note Mithril networks + + + +::: + +## Resources + +| Node | Source repository | Rust documentation | +|:-:|:-----------------:|:------------------:| +**Mithril client WASM** | [:arrow_upper_right:](https://github.com/input-output-hk/mithril/tree/main/mithril-client-wasm) | [:arrow_upper_right:](https://mithril.network/rust-doc/mithril_client_wasm/index.html) | + + +## Installation + +The Mithril client library is compatible with the following browsers: + +| Browser | Minimum version | Released | Tested in CI | +| --- |:---:|:---:|:---:| +| **Chrome** | `54` | 2016-10-12 | :heavy_check_mark: | +| **Edge** | `79` | 2020-01-15 | - | +| **Firefox** | `38` | 2015-05-12 | :heavy_check_mark: | +| **Opera** | `41` | 2016-10-25 | - | +| **Safari** | `15.4` | 2022-03-14 | - | +| **Chrome Android** | `54` | 2016-10-19 | - | +| **Firefox for Android** | `38` | 2015-05-12 | - | +| **Opera Android** | `41` | 2016-10-25 | - | +| **Safari on iOS** | `15.4` | 2022-03-14 | - | + + +:::warning + +The package is not yet released (it will be published soon on [npm](https://www.npmjs.com/)). In the meantime, you can test the librayr by compiling it from [Mithril repository](https://github.com/input-output-hk/mithril/tree/main/mithril-client-wasm). + +::: + +In your Javascript project, use `npm` to add [mithril-client]https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm) library as a dependency: + +```bash +npm i @mithril-dev/mithril-client-wasm +``` + +## Using Mithril client library + +Below is a basic example of how to use most of the functions exposed by the Mithril client library: + +```js +import { MithrilClient } from "@mithril-dev/mithril-client-wasm" + +let aggregator_endpoint = + "https://aggregator.testing-preview.api.mithril.network/aggregator" +let genesis_verification_key = + "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d" + +const broadcast_channel = new BroadcastChannel("mithril-client"); +broadcast_channel.onmessage = (e) => { + let event = e.data; + if (event.type == "CertificateChainValidationStarted") { + console.log("The certificate chain validation has started"); + } else if (event.type == "CertificateValidated") { + console.log("A certificate has been validated, certificate_hash: " + event.payload.certificate_hash); + } else if (event.type == "CertificateChainValidated") { + console.log("The certificate chain is valid"); + } else { + console.log(event); + } +}; + +let client = await new MithrilClient( + aggregator_endpoint, + genesis_verification_key +) +let mithril_stake_distributions_list = await client.list_mithril_stake_distributions(); +console.log("stake distributions:", mithril_stake_distributions_list); + +let last_mithril_stake_distribution = mithril_stake_distributions_list[0]; +console.log("last_mithril_stake_distribution:", last_mithril_stake_distribution); + +let last_stake_distribution = await client.get_mithril_stake_distribution(last_mithril_stake_distribution.hash); +console.log("last_stake_distribution:", last_stake_distribution); + +let certificate = await client.get_mithril_certificate(last_stake_distribution.certificate_hash); +console.log("certificate:", certificate); + +let last_certificate_from_chain = await client.verify_certificate_chain(certificate.hash); +console.log("verify certificate chain OK, last_certificate_from_chain:", last_certificate_from_chain); + +let mithril_stake_distributions_message = await client.compute_mithril_stake_distribution_message(last_stake_distribution); +console.log("mithril_stake_distributions_message:", mithril_stake_distributions_message); + +let valid_stake_distribution_message = await client.verify_message_match_certificate(mithril_stake_distributions_message, last_certificate_from_chain); +console.log("valid_stake_distribution_message:", valid_stake_distribution_message); +``` + +:::tip + +You can read the complete [Rust developer documentation](https://mithril.network/rust-doc/mithril_client_wasm/index.html). + +::: \ No newline at end of file diff --git a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library.md b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library.md index 8ce3e4bec78..7fa5bade0cc 100644 --- a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library.md +++ b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library.md @@ -12,7 +12,7 @@ import CompiledBinaries from '../../../compiled-binaries.md' Mithril client library can be used by Rust developers to use the Mithril network in their applications. It is responsible for handling the different types of data certified by Mithril, and available through a Mithril aggregator: -- [**Snapshot**](../../../glossary.md#snapshot): list, get and download tarball. +- [**Snapshot**](../../../glossary.md#snapshot): list, get, download tarball and record statistics. - [**Mithril stake distribution**](../../../glossary.md#stake-distribution): list and get. - [**Certificate**](../../../glossary.md#certificate): list, get, and chain validation. @@ -88,6 +88,10 @@ async fn main() -> mithril_client::MithrilResult<()> { .snapshot() .download_unpack(&snapshot, target_directory) .await?; + + if let Err(e) = client.snapshot().add_statistics(&snapshot).await { + println!("Could not increment snapshot download statistics: {:?}", e); + } let message = MessageBuilder::new() .compute_snapshot_message(&certificate, target_directory) @@ -102,10 +106,16 @@ async fn main() -> mithril_client::MithrilResult<()> { Snapshot download and certificate chain validation can take quite some time even with a fast computer and network. We have implemented a feedback mechanism for them, more details on it are available in the [feedback sub-module](https://mithril.network/rust-doc/mithril_client/feedback/index.html). -An example of implementation with the crate [indicatif](https://crates.io/crates/indicatif) is available in the [Mithril repository](https://github.com/input-output-hk/mithril/tree/main/examples/client-snapshot). To run it, execute the following command: +An example of implementation with the crate [indicatif](https://crates.io/crates/indicatif) is available in the [Mithril repository](https://github.com/input-output-hk/mithril/tree/main/examples/client-snapshot/src/main.rs). To run it, execute the following command: + +```bash +cargo run -p client-snapshot +``` + +or directly from the example crate directory: ```bash -cargo run --example snapshot_list_get_show_download_verify --features fs +cargo run ``` ::: @@ -136,6 +146,10 @@ async fn main() -> mithril_client::MithrilResult<()> { .snapshot() .download_unpack(&snapshot, target_directory) .await?; + + if let Err(e) = client.snapshot().add_statistics(&snapshot).await { + println!("Could not increment snapshot download statistics: {:?}", e); + } let message = MessageBuilder::new() .compute_snapshot_message(&certificate, target_directory) diff --git a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client.md b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client.md index 9a99906e911..9646844ef43 100644 --- a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client.md +++ b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client.md @@ -137,6 +137,10 @@ Options: Directory where configuration file is located [default: ./config] --aggregator-endpoint Override configuration Aggregator endpoint URL + --log-format-json + Enable JSON output for logs displayed according to verbosity level + --log-output + Redirect the logs to a file -h, --help Print help -V, --version @@ -296,13 +300,21 @@ Here is a list of the available parameters: | `network` | - | - | `NETWORK` | Cardano network | - | `testnet` or `mainnet` or `devnet` | :heavy_check_mark: | | `aggregator_endpoint` | `--aggregator-endpoint` | - | `AGGREGATOR_ENDPOINT` | Aggregator node endpoint | - | `https://aggregator.pre-release-preview.api.mithril.network/aggregator` | :heavy_check_mark: | | `genesis_verification_key` | - | - | `GENESIS_VERIFICATION_KEY` | Genesis verification key | - | - | :heavy_check_mark: | -| `json_output` | `--json` | `-j` | - | Enable JSON output | no | - | - | +| `log_format_json` | `--log-format-json` | - | - | Enable JSON output for logs | - | - | - | +| `log_output` | `--log-output` | `-o` | - | Redirect the logs to a file | - | `./mithril-client.log` | - | `snapshot show` command: | Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | |-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:| | `digest` | `--digest` | - | `DIGEST` | Snapshot digest or `latest` for the latest digest | - | - | :heavy_check_mark: | +| `json` | `--json` | - | - | Enable JSON output for command results | - | - | - | + +`snapshot list` command: + +| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | +|-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:| +| `json` | `--json` | - | - | Enable JSON output for command results | - | - | - | `snapshot download` command: @@ -310,6 +322,13 @@ Here is a list of the available parameters: |-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:| | `digest` | `--digest` | - | `DIGEST` | Snapshot digest or `latest` for the latest digest | - | - | :heavy_check_mark: | | `download_dir` | `--download-dir` | - | - | Directory where the snapshot will be downloaded | . | - | - | +| `json` | `--json` | - | - | Enable JSON output for progress logs | - | - | - | + +`mithril-stake-distribution list` command: + +| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | +|-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:| +| `json` | `--json` | - | - | Enable JSON output for command results | - | - | - | `mithril-stake-distribution download` command: diff --git a/docs/website/versioned_docs/version-maintained/manual/getting-started/bootstrap-cardano-node.md b/docs/website/versioned_docs/version-maintained/manual/getting-started/bootstrap-cardano-node.md index 56573cc6806..9e7f6ffcef8 100644 --- a/docs/website/versioned_docs/version-maintained/manual/getting-started/bootstrap-cardano-node.md +++ b/docs/website/versioned_docs/version-maintained/manual/getting-started/bootstrap-cardano-node.md @@ -281,7 +281,7 @@ You will see more information about the snapshot: +-----------------------+-------------------------------------------------------------------------------------------------------------------------------+ | Location 1 | https://storage.googleapis.com/mithril-release-preprod-…aa11b0e2ccf737d4f5def8b0a9f2245eded2b4ec4be876f7bd64deddcbbf.tar.gz | +-----------------------+-------------------------------------------------------------------------------------------------------------------------------+ -| Cardano node version | 8.1.2 | +| Cardano node version | 8.7.3 | +-----------------------+-------------------------------------------------------------------------------------------------------------------------------+ | Created | 2023-05-31T14:02:40.150189810Z | +-----------------------+-------------------------------------------------------------------------------------------------------------------------------+ diff --git a/docs/website/versioned_docs/version-maintained/manual/getting-started/run-signer-node.md b/docs/website/versioned_docs/version-maintained/manual/getting-started/run-signer-node.md index 62f5c38f94e..c72a3bea5fb 100644 --- a/docs/website/versioned_docs/version-maintained/manual/getting-started/run-signer-node.md +++ b/docs/website/versioned_docs/version-maintained/manual/getting-started/run-signer-node.md @@ -77,7 +77,7 @@ Note that this guide works on a Linux machine only. * Read rights on the `Database` folder (specified by the `--database-path` setting of the **Cardano node**) * Read and write rights on the `Inter Process Communication` file (typically defined by the `CARDANO_NODE_SOCKET_PATH` environment variable used to launch the **Cardano node**) -* Install a recent version of [`cardano-cli`](https://github.com/input-output-hk/cardano-node/releases/tag/8.1.2) (version 8.1.2+). +* Install a recent version of [`cardano-cli`](https://github.com/input-output-hk/cardano-node/releases/tag/8.7.3) (version 8.7.3+). * Install a correctly configured Rust toolchain (latest stable version). You can follow the instructions provided [here](https://www.rust-lang.org/learn/get-started). From 9502107b51f8b7a07395f83c938bb6636e564807 Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Mon, 22 Jan 2024 12:26:33 +0100 Subject: [PATCH 02/25] Fix `mithril-client wasm` README.md published on `npm` --- mithril-client-wasm/npm/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mithril-client-wasm/npm/README.md b/mithril-client-wasm/npm/README.md index 0b8bddeda7c..7854d8306d7 100644 --- a/mithril-client-wasm/npm/README.md +++ b/mithril-client-wasm/npm/README.md @@ -14,7 +14,7 @@ Below is a basic example of how to use most of the functions exposed by the Mithril client WASM library: ```javascript -import { MithrilClient } from "@mithril-dev/mithril-client-wasm" +import initMithrilClient, { MithrilClient } from "@mithril-dev/mithril-client-wasm" let aggregator_endpoint = "https://aggregator.testing-preview.api.mithril.network/aggregator" @@ -35,6 +35,8 @@ broadcast_channel.onmessage = (e) => { } }; +await initMithrilClient(); + let client = await new MithrilClient( aggregator_endpoint, genesis_verification_key From 8e89ddea22bb29b09357f189d9a47ba8343d6f82 Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Mon, 22 Jan 2024 12:33:44 +0100 Subject: [PATCH 03/25] Update same code example in the dev documentation --- .../developer-docs/nodes/mithril-client-library-wasm.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md b/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md index fa43727e1ca..7483cced94c 100644 --- a/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md +++ b/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md @@ -71,7 +71,7 @@ npm i @mithril-dev/mithril-client-wasm Below is a basic example of how to use most of the functions exposed by the Mithril client library: ```js -import { MithrilClient } from "@mithril-dev/mithril-client-wasm" +import initMithrilClient, { MithrilClient } from "@mithril-dev/mithril-client-wasm" let aggregator_endpoint = "https://aggregator.testing-preview.api.mithril.network/aggregator" @@ -92,6 +92,8 @@ broadcast_channel.onmessage = (e) => { } }; +await initMithrilClient(); + let client = await new MithrilClient( aggregator_endpoint, genesis_verification_key From 36eecda1bdd174902ce204e978c452298d3cedbb Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:26:54 +0100 Subject: [PATCH 04/25] Specify explicitly permissions for prereleaes creation This is a tentative fix to avoid 403 errors when the tag is not the head of any branchs. --- .github/workflows/pre-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 9fde606d62d..98e67bbc102 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -9,6 +9,8 @@ on: jobs: create-pre-release: runs-on: ubuntu-22.04 + permissions: + contents: write steps: - name: Checkout sources uses: actions/checkout@v3 From 6215516f18978a56d92b95e3998e3dbad475878c Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Tue, 23 Jan 2024 14:36:34 +0100 Subject: [PATCH 05/25] Update crate version --- Cargo.lock | 2 +- mithril-client-wasm/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d13b7d8b939..c371662a27f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3381,7 +3381,7 @@ dependencies = [ [[package]] name = "mithril-client-wasm" -version = "0.1.6" +version = "0.1.7" dependencies = [ "async-trait", "futures", diff --git a/mithril-client-wasm/Cargo.toml b/mithril-client-wasm/Cargo.toml index 48ad59e4e7f..c438e901d9c 100644 --- a/mithril-client-wasm/Cargo.toml +++ b/mithril-client-wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-client-wasm" -version = "0.1.6" +version = "0.1.7" description = "Mithril client WASM" authors = { workspace = true } edition = { workspace = true } From 4b3eab8ff5f162ecc68ef9f6721aa81078c4931f Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 15:18:31 +0100 Subject: [PATCH 06/25] fix: remove the mention to unpublished npm package --- .../developer-docs/nodes/mithril-client-library-wasm.md | 9 +-------- .../developer-docs/nodes/mithril-client-library-wasm.md | 7 ------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md b/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md index 7483cced94c..ecf3aee4ab9 100644 --- a/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md +++ b/docs/website/root/manual/developer-docs/nodes/mithril-client-library-wasm.md @@ -53,14 +53,7 @@ The Mithril client library is compatible with the following browsers: | **Opera Android** | `41` | 2016-10-25 | - | | **Safari on iOS** | `15.4` | 2022-03-14 | - | - -:::warning - -The package is not yet released (it will be published soon on [npm](https://www.npmjs.com/)). In the meantime, you can test the librayr by compiling it from [Mithril repository](https://github.com/input-output-hk/mithril/tree/main/mithril-client-wasm). - -::: - -In your Javascript project, use `npm` to add [mithril-client]https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm) library as a dependency: +In your Javascript project, use `npm` to add [mithril-client-wasm](https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm) library as a dependency: ```bash npm i @mithril-dev/mithril-client-wasm diff --git a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md index fa43727e1ca..833ed457d1e 100644 --- a/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md +++ b/docs/website/versioned_docs/version-maintained/manual/developer-docs/nodes/mithril-client-library-wasm.md @@ -53,13 +53,6 @@ The Mithril client library is compatible with the following browsers: | **Opera Android** | `41` | 2016-10-25 | - | | **Safari on iOS** | `15.4` | 2022-03-14 | - | - -:::warning - -The package is not yet released (it will be published soon on [npm](https://www.npmjs.com/)). In the meantime, you can test the librayr by compiling it from [Mithril repository](https://github.com/input-output-hk/mithril/tree/main/mithril-client-wasm). - -::: - In your Javascript project, use `npm` to add [mithril-client]https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm) library as a dependency: ```bash From 985fcb67bff3c48269d273a3e63fcd04f3adc430 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 15:43:17 +0100 Subject: [PATCH 07/25] feat: add dev blog post for package on published on npmjs.com --- ...-23-mithril-client-npm-package-released.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/website/blog/2024-01-23-mithril-client-npm-package-released.md diff --git a/docs/website/blog/2024-01-23-mithril-client-npm-package-released.md b/docs/website/blog/2024-01-23-mithril-client-npm-package-released.md new file mode 100644 index 00000000000..353547ee673 --- /dev/null +++ b/docs/website/blog/2024-01-23-mithril-client-npm-package-released.md @@ -0,0 +1,19 @@ +--- +title: Mithril client npm package is released! +authors: + - name: Mithril Team +tags: [wasm, web assembly, mithril client, library, npm, registry] +--- + +### Mithril client npm is open to developers on npmjs.com + +The Mithril team has published the first release (**v0.1.7)** of its [mithril-client-wasm](https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm) npm package on npmjs.com. + +The **Mithril client npm package** allows developers to use all the tooling necessary to manipulate Mithril certified types from a Mithril Aggregator directly in their (web) applications and to develop their own Mithril clients in any supported browser! + +Here are some useful documentation that can be used by developers: +- **Example of implementations**: https://mithril.network/doc/manual/developer-docs/nodes/mithril-client-library-wasm +- **Mithril client npm package repository**: https://github.com/input-output-hk/mithril/tree/main/mithril-client-wasm +- **npm**: https://www.npmjs.com/package/@mithril-dev/mithril-client-wasm + +Feel free to reach out to us on the [Discord channel](https://discord.gg/5kaErDKDRq) for questions and/or help. From 7a80862cc53e9e6ffe3fab2ec04cad2aaafc3436 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 18 Jan 2024 11:19:58 +0100 Subject: [PATCH 08/25] feat: create a store for Cardano transactions in aggregator --- .../cardano_transactions_migration.rs | 26 ++++++++ mithril-aggregator/src/database/mod.rs | 2 + .../database/provider/cardano_transactions.rs | 20 +++++++ .../src/database/provider/mod.rs | 2 + .../src/dependency_injection/builder.rs | 60 ++++++++++++++++--- .../src/dependency_injection/containers.rs | 3 + 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 mithril-aggregator/src/database/cardano_transactions_migration.rs create mode 100644 mithril-aggregator/src/database/provider/cardano_transactions.rs diff --git a/mithril-aggregator/src/database/cardano_transactions_migration.rs b/mithril-aggregator/src/database/cardano_transactions_migration.rs new file mode 100644 index 00000000000..116d8ea9933 --- /dev/null +++ b/mithril-aggregator/src/database/cardano_transactions_migration.rs @@ -0,0 +1,26 @@ +//! Migration module for cardano transactions store +//! +use mithril_common::database::SqlMigration; + +/// Get all the migrations required by this version of the software. +/// There shall be one migration per database version. There could be several +/// statements per migration. +pub fn get_migrations() -> Vec { + vec![ + // Migration 1 + // Add the `cardano_tx` table. + SqlMigration::new( + 1, + r#" +create table cardano_tx ( + transaction_hash text not null, + block_number integer not null, + immutable_file_number integer not null, + primary key (transaction_hash) +); + +create unique index cardano_transactions_unique_index on cardano_tx(immutable_file_number); +"#, + ), + ] +} diff --git a/mithril-aggregator/src/database/mod.rs b/mithril-aggregator/src/database/mod.rs index ef99498cf6a..ff8a5d73d29 100644 --- a/mithril-aggregator/src/database/mod.rs +++ b/mithril-aggregator/src/database/mod.rs @@ -1,5 +1,7 @@ //! database module. //! This module contains the entities definition tied with database //! representation with their associated providers. + +pub mod cardano_transactions_migration; pub mod migration; pub mod provider; diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transactions.rs new file mode 100644 index 00000000000..4f30af9a54a --- /dev/null +++ b/mithril-aggregator/src/database/provider/cardano_transactions.rs @@ -0,0 +1,20 @@ +use mithril_common::entities::ImmutableFileNumber; + +/// TransactionHash is the unique identifier of a cardano transaction. +pub type TransactionHash = String; + +/// BlockNumber is the block number of a cardano transaction. +pub type BlockNumber = u64; + +/// Cardano Transaction record is the representation of a cardano transaction. +#[derive(Debug, PartialEq, Clone)] +pub struct CardanoTransactionRecord { + /// Unique hash of the transaction + pub transaction_hash: TransactionHash, + + /// Block number of the transaction + pub block_number: BlockNumber, + + /// Immutable file number of the transaction + pub immutable_file_number: ImmutableFileNumber, +} diff --git a/mithril-aggregator/src/database/provider/mod.rs b/mithril-aggregator/src/database/provider/mod.rs index 271a37f2634..b2165c64d54 100644 --- a/mithril-aggregator/src/database/provider/mod.rs +++ b/mithril-aggregator/src/database/provider/mod.rs @@ -1,4 +1,5 @@ //! Aggregator related database providers +mod cardano_transactions; mod certificate; mod epoch_setting; mod open_message; @@ -10,6 +11,7 @@ mod stake_pool; #[cfg(test)] mod test_helper; +pub use cardano_transactions::*; pub use certificate::*; pub use epoch_setting::*; pub use open_message::*; diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index f094e50d050..56bd67b2694 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -1,10 +1,9 @@ -use std::sync::Arc; - use anyhow::Context; use semver::Version; use slog::Logger; use slog_scope::debug; use sqlite::Connection; +use std::sync::Arc; use tokio::{ sync::{ mpsc::{UnboundedReceiver, UnboundedSender}, @@ -21,7 +20,7 @@ use mithril_common::{ crypto_helper::{ ProtocolGenesisSigner, ProtocolGenesisVerificationKey, ProtocolGenesisVerifier, }, - database::{ApplicationNodeType, DatabaseVersionChecker}, + database::{ApplicationNodeType, DatabaseVersionChecker, SqlMigration}, digesters::{ cache::{ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProviderBuilder}, CardanoImmutableDigester, DumbImmutableFileObserver, ImmutableDigester, @@ -72,6 +71,7 @@ use crate::{ use super::{DependenciesBuilderError, EpochServiceWrapper, Result}; const SQLITE_FILE: &str = "aggregator.sqlite3"; +const SQLITE_FILE_CARDANO_TRANSACTIONS: &str = "cardano-transactions.sqlite3"; /// ## Dependencies container builder /// @@ -91,6 +91,9 @@ pub struct DependenciesBuilder { /// SQLite database connection pub sqlite_connection: Option>, + /// Cardano transactions SQLite database connection + pub cardano_transactions_sqlite_connection: Option>, + /// Stake Store used by the StakeDistributionService /// It shall be a private dependency. pub stake_store: Option>, @@ -204,6 +207,7 @@ impl DependenciesBuilder { Self { configuration, sqlite_connection: None, + cardano_transactions_sqlite_connection: None, stake_store: None, snapshot_uploader: None, multi_signer: None, @@ -241,10 +245,14 @@ impl DependenciesBuilder { } } - async fn build_sqlite_connection(&self) -> Result> { + async fn build_sqlite_connection( + &self, + sqlite_file_name: &str, + migrations: Vec, + ) -> Result> { let path = match self.configuration.environment { ExecutionEnvironment::Production => { - self.configuration.get_sqlite_dir().join(SQLITE_FILE) + self.configuration.get_sqlite_dir().join(sqlite_file_name) } _ => self.configuration.data_stores_directory.clone(), }; @@ -266,7 +274,7 @@ impl DependenciesBuilder { connection.as_ref(), ); - for migration in crate::database::migration::get_migrations() { + for migration in migrations { db_checker.add_migration(migration); } @@ -293,21 +301,52 @@ impl DependenciesBuilder { Ok(connection) } - async fn drop_sqlite_connection(&self) { + async fn drop_sqlite_connections(&self) { if let Some(connection) = &self.sqlite_connection { let _ = connection.execute("pragma analysis_limit=400; pragma optimize;"); } + + if let Some(connection) = &self.cardano_transactions_sqlite_connection { + let _ = connection.execute("pragma analysis_limit=400; pragma optimize;"); + } } /// Get SQLite connection pub async fn get_sqlite_connection(&mut self) -> Result> { if self.sqlite_connection.is_none() { - self.sqlite_connection = Some(self.build_sqlite_connection().await?); + self.sqlite_connection = Some( + self.build_sqlite_connection( + SQLITE_FILE, + crate::database::migration::get_migrations(), + ) + .await?, + ); } Ok(self.sqlite_connection.as_ref().cloned().unwrap()) } + /// Get SQLite connection for the cardano transactions store + pub async fn get_sqlite_connection_cardano_transactions( + &mut self, + ) -> Result> { + if self.cardano_transactions_sqlite_connection.is_none() { + self.cardano_transactions_sqlite_connection = Some( + self.build_sqlite_connection( + SQLITE_FILE_CARDANO_TRANSACTIONS, + crate::database::cardano_transactions_migration::get_migrations(), + ) + .await?, + ); + } + + Ok(self + .cardano_transactions_sqlite_connection + .as_ref() + .cloned() + .unwrap()) + } + async fn build_stake_store(&mut self) -> Result> { let stake_pool_store = Arc::new(StakePoolStore::new( self.get_sqlite_connection().await?, @@ -1043,6 +1082,9 @@ impl DependenciesBuilder { let dependency_manager = DependencyContainer { config: self.configuration.clone(), sqlite_connection: self.get_sqlite_connection().await?, + sqlite_connection_cardano_transactions: self + .get_sqlite_connection_cardano_transactions() + .await?, stake_store: self.get_stake_store().await?, snapshot_uploader: self.get_snapshot_uploader().await?, multi_signer: self.get_multi_signer().await?, @@ -1279,6 +1321,6 @@ impl DependenciesBuilder { /// Remove the dependencies builder from memory to release Arc instances. pub async fn vanish(self) { - self.drop_sqlite_connection().await; + self.drop_sqlite_connections().await; } } diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index a1c1c2fa8c8..b6c9396d37d 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -48,6 +48,9 @@ pub struct DependencyContainer { /// services. Shall be private dependency. pub sqlite_connection: Arc, + /// SQLite database connection for Cardano transactions + pub sqlite_connection_cardano_transactions: Arc, + /// Stake Store used by the StakeDistributionService /// It shall be a private dependency. pub stake_store: Arc, From 11696c0d21c75a086a6aeaef6e9f2a325f19ebbc Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 18 Jan 2024 12:02:15 +0100 Subject: [PATCH 09/25] feat: implement Cardano transactions database provider --- .../database/provider/cardano_transactions.rs | 276 +++++++++++++++++- 1 file changed, 275 insertions(+), 1 deletion(-) diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transactions.rs index 4f30af9a54a..ab23372ed40 100644 --- a/mithril-aggregator/src/database/provider/cardano_transactions.rs +++ b/mithril-aggregator/src/database/provider/cardano_transactions.rs @@ -1,4 +1,14 @@ -use mithril_common::entities::ImmutableFileNumber; +use mithril_common::{ + entities::ImmutableFileNumber, + sqlite::{ + HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, + WhereCondition, + }, + StdResult, +}; + +use sqlite::{Row, Value}; +use std::sync::Arc; /// TransactionHash is the unique identifier of a cardano transaction. pub type TransactionHash = String; @@ -18,3 +28,267 @@ pub struct CardanoTransactionRecord { /// Immutable file number of the transaction pub immutable_file_number: ImmutableFileNumber, } + +impl SqLiteEntity for CardanoTransactionRecord { + fn hydrate(row: Row) -> Result + where + Self: Sized, + { + let transaction_hash = row.read::<&str, _>(0); + let block_number = row.read::(1); + let block_number = u64::try_from(block_number) + .map_err(|e| HydrationError::InvalidData(format!("Integer field cardano_tx.block_number (value={block_number}) is incompatible with u64 representation. Error = {e}")))?; + let immutable_file_number = row.read::(2); + let immutable_file_number = u64::try_from(immutable_file_number) + .map_err(|e| HydrationError::InvalidData(format!("Integer field cardano_tx.immutable_file_number (value={immutable_file_number}) is incompatible with u64 representation. Error = {e}")))?; + + Ok(Self { + transaction_hash: transaction_hash.to_string(), + block_number, + immutable_file_number, + }) + } + + fn get_projection() -> Projection { + Projection::from(&[ + ( + "transaction_hash", + "{:cardano_tx:}.transaction_hash", + "text", + ), + ("block_number", "{:cardano_tx:}.block_number", "int"), + ( + "immutable_file_number", + "{:cardano_tx:}.immutable_file_number", + "int", + ), + ]) + } +} + +struct CardanoTransactionProvider<'client> { + connection: &'client SqliteConnection, +} + +impl<'client> CardanoTransactionProvider<'client> { + pub fn new(connection: &'client SqliteConnection) -> Self { + Self { connection } + } + + // Useful in test and probably in the future. + #[allow(dead_code)] + fn get_transaction_hash_condition(&self, transaction_hash: &TransactionHash) -> WhereCondition { + WhereCondition::new( + "transaction_hash = ?*", + vec![Value::String(transaction_hash.to_owned())], + ) + } +} + +impl<'client> Provider<'client> for CardanoTransactionProvider<'client> { + type Entity = CardanoTransactionRecord; + + fn get_connection(&'client self) -> &'client SqliteConnection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!( + "select {projection} from cardano_tx where {condition} order by transaction_hash desc" + ) + } +} + +struct InsertCardanoTransactionProvider<'client> { + connection: &'client SqliteConnection, +} + +impl<'client> InsertCardanoTransactionProvider<'client> { + pub fn new(connection: &'client SqliteConnection) -> Self { + Self { connection } + } + + fn get_insert_condition(&self, record: &CardanoTransactionRecord) -> StdResult { + let expression = + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3)"; + let parameters = vec![ + Value::String(record.transaction_hash.clone()), + Value::Integer(record.block_number.try_into()?), + Value::Integer(record.immutable_file_number.try_into()?), + ]; + + Ok(WhereCondition::new(expression, parameters)) + } +} + +impl<'client> Provider<'client> for InsertCardanoTransactionProvider<'client> { + type Entity = CardanoTransactionRecord; + + fn get_connection(&'client self) -> &'client SqliteConnection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!("insert into cardano_tx {condition} returning {projection}") + } +} + +/// ## Cardano transaction repository +/// +/// This is a business oriented layer to perform actions on the database through +/// providers. +pub struct CardanoTransactionRepository { + connection: Arc, +} + +impl CardanoTransactionRepository { + /// Instantiate service + pub fn new(connection: Arc) -> Self { + Self { connection } + } + + /// Return the [CardanoTransactionRecord] for the given transaction hash. + pub async fn get_transaction( + &self, + transaction_hash: &TransactionHash, + ) -> StdResult> { + let provider = CardanoTransactionProvider::new(&self.connection); + let filters = provider.get_transaction_hash_condition(transaction_hash); + let mut transactions = provider.find(filters)?; + + Ok(transactions.next()) + } + + /// Create a new [CardanoTransactionRecord] in the database. + pub async fn create_transaction( + &self, + transaction_hash: &TransactionHash, + block_number: BlockNumber, + immutable_file_number: ImmutableFileNumber, + ) -> StdResult { + let provider = InsertCardanoTransactionProvider::new(&self.connection); + let filters = provider.get_insert_condition(&CardanoTransactionRecord { + transaction_hash: transaction_hash.to_owned(), + block_number, + immutable_file_number, + })?; + let mut cursor = provider.find(filters)?; + + cursor + .next() + .ok_or_else(|| panic!("Inserting a Cardano transaction should not return nothing.")) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::sqlite::SourceAlias; + use sqlite::Connection; + + use crate::{dependency_injection::DependenciesBuilder, Configuration}; + + use super::*; + + async fn get_connection() -> Arc { + let config = Configuration::new_sample(); + let mut builder = DependenciesBuilder::new(config); + builder + .get_sqlite_connection_cardano_transactions() + .await + .unwrap() + } + + #[test] + fn cardano_transactions_projection() { + let projection = CardanoTransactionRecord::get_projection(); + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + + assert_eq!( + "cardano_tx.transaction_hash as transaction_hash, cardano_tx.block_number as block_number, cardano_tx.immutable_file_number as immutable_file_number".to_string(), + projection.expand(aliases) + ) + } + + #[test] + fn provider_transaction_hash_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = CardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_transaction_hash_condition( + &"0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string(), + ) + .expand(); + + assert_eq!("transaction_hash = ?1".to_string(), expr); + assert_eq!( + vec![Value::String( + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string() + )], + params, + ); + } + + #[test] + fn insert_provider_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = InsertCardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_insert_condition(&CardanoTransactionRecord { + transaction_hash: + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string(), + block_number: 10, + immutable_file_number: 99, + }) + .unwrap() + .expand(); + + assert_eq!( + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3)" + .to_string(), + expr + ); + assert_eq!( + vec![ + Value::String( + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string() + ), + Value::Integer(10), + Value::Integer(99) + ], + params + ); + } + + #[tokio::test] + async fn repository_create_and_get_transaction() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + repository + .create_transaction(&"tx-hash-123".to_string(), 10, 99) + .await + .unwrap(); + repository + .create_transaction(&"tx-hash-456".to_string(), 11, 100) + .await + .unwrap(); + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + } +} From f9566f0dd978d642714b635555fd70d3c7ae3f88 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 18 Jan 2024 16:54:45 +0100 Subject: [PATCH 10/25] feat: implementation Cardano transactions signable builder With Cardano transaction parser that gathers transactions from immutable files. --- Cargo.lock | 70 +++++ .../database/provider/cardano_transactions.rs | 8 +- .../src/dependency_injection/builder.rs | 33 ++- .../src/dependency_injection/containers.rs | 4 + mithril-common/Cargo.toml | 3 +- .../src/cardano_transactions_parser.rs | 265 ++++++++++++++++++ .../src/entities/cardano_transaction.rs | 20 ++ mithril-common/src/entities/mod.rs | 2 + mithril-common/src/lib.rs | 8 + .../signable_builder/cardano_transactions.rs | 66 ++++- mithril-signer/src/runtime/runner.rs | 9 +- mithril-signer/src/runtime/signer_services.rs | 9 +- .../test_extensions/state_machine_tester.rs | 12 +- mithril-test-lab/test_data/README.md | 68 +++++ .../test_data/blocks/allegra1.block | 1 + .../test_data/blocks/alonzo1.block | 1 + .../test_data/blocks/byron2.block | 1 + mithril-test-lab/test_data/blocks/mary1.block | 1 + .../test_data/blocks/shelley1.block | 1 + .../test_data/immutable/00000.chunk | Bin 0 -> 25550 bytes .../test_data/immutable/00000.primary | 0 .../test_data/immutable/00000.secondary | 0 .../test_data/immutable/00001.chunk | Bin 0 -> 5925 bytes .../test_data/immutable/00001.primary | 0 .../test_data/immutable/00001.secondary | 0 .../test_data/immutable/00002.chunk | 0 .../test_data/immutable/00002.primary | 0 .../test_data/immutable/00002.secondary | 0 28 files changed, 560 insertions(+), 22 deletions(-) create mode 100644 mithril-common/src/cardano_transactions_parser.rs create mode 100644 mithril-common/src/entities/cardano_transaction.rs create mode 100644 mithril-test-lab/test_data/README.md create mode 100644 mithril-test-lab/test_data/blocks/allegra1.block create mode 100644 mithril-test-lab/test_data/blocks/alonzo1.block create mode 100644 mithril-test-lab/test_data/blocks/byron2.block create mode 100644 mithril-test-lab/test_data/blocks/mary1.block create mode 100644 mithril-test-lab/test_data/blocks/shelley1.block create mode 100644 mithril-test-lab/test_data/immutable/00000.chunk create mode 100644 mithril-test-lab/test_data/immutable/00000.primary create mode 100644 mithril-test-lab/test_data/immutable/00000.secondary create mode 100644 mithril-test-lab/test_data/immutable/00001.chunk create mode 100644 mithril-test-lab/test_data/immutable/00001.primary create mode 100644 mithril-test-lab/test_data/immutable/00001.secondary create mode 100644 mithril-test-lab/test_data/immutable/00002.chunk create mode 100644 mithril-test-lab/test_data/immutable/00002.primary create mode 100644 mithril-test-lab/test_data/immutable/00002.secondary diff --git a/Cargo.lock b/Cargo.lock index c371662a27f..b2c59ba999b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,6 +594,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base64" version = "0.12.3" @@ -1073,6 +1079,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -3417,6 +3438,7 @@ dependencies = [ "nom", "pallas-codec", "pallas-network", + "pallas-traverse", "rand_chacha", "rand_core 0.6.4", "rayon", @@ -4019,6 +4041,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pallas-addresses" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "448cdac132c6a90f166098b3739d32f76c2a44ad7db443c1038db1ef57bef0c3" +dependencies = [ + "base58", + "bech32", + "crc", + "hex", + "pallas-codec", + "pallas-crypto", + "sha3", + "thiserror", +] + [[package]] name = "pallas-codec" version = "0.21.0" @@ -4063,6 +4101,38 @@ dependencies = [ "tracing", ] +[[package]] +name = "pallas-primitives" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcfeb1663613296e09b6a0a81b49e6547402271f5159031c7bb9bc83e9014e9" +dependencies = [ + "base58", + "bech32", + "hex", + "log", + "pallas-codec", + "pallas-crypto", + "serde", + "serde_json", +] + +[[package]] +name = "pallas-traverse" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7914e34c26adb83c0595133281492b8c03a08c214301fa2314e996ece91a6ba7" +dependencies = [ + "hex", + "pallas-addresses", + "pallas-codec", + "pallas-crypto", + "pallas-primitives", + "paste", + "serde", + "thiserror", +] + [[package]] name = "parking" version = "2.2.0" diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transactions.rs index ab23372ed40..55be900dc46 100644 --- a/mithril-aggregator/src/database/provider/cardano_transactions.rs +++ b/mithril-aggregator/src/database/provider/cardano_transactions.rs @@ -1,5 +1,5 @@ use mithril_common::{ - entities::ImmutableFileNumber, + entities::{BlockNumber, ImmutableFileNumber, TransactionHash}, sqlite::{ HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition, @@ -10,12 +10,6 @@ use mithril_common::{ use sqlite::{Row, Value}; use std::sync::Arc; -/// TransactionHash is the unique identifier of a cardano transaction. -pub type TransactionHash = String; - -/// BlockNumber is the block number of a cardano transaction. -pub type BlockNumber = u64; - /// Cardano Transaction record is the representation of a cardano transaction. #[derive(Debug, PartialEq, Clone)] pub struct CardanoTransactionRecord { diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 56bd67b2694..e23233a966e 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -15,6 +15,7 @@ use warp::Filter; use mithril_common::{ api_version::APIVersionProvider, + cardano_transactions_parser::TransactionParser, certificate_chain::{CertificateVerifier, MithrilCertificateVerifier}, chain_observer::{CardanoCliRunner, ChainObserver, ChainObserverBuilder, FakeObserver}, crypto_helper::{ @@ -128,6 +129,9 @@ pub struct DependenciesBuilder { /// Beacon provider service. pub beacon_provider: Option>, + /// Cardano transactions parser. + pub transaction_parser: Option>, + /// Immutable file digester service. pub immutable_digester: Option>, @@ -219,6 +223,7 @@ impl DependenciesBuilder { cardano_cli_runner: None, chain_observer: None, beacon_provider: None, + transaction_parser: None, immutable_digester: None, immutable_file_observer: None, immutable_cache_provider: None, @@ -659,6 +664,27 @@ impl DependenciesBuilder { self.create_logger().await } + async fn build_transaction_parser(&mut self) -> Result> { + todo!() + /* let immutable_digester_cache = match self.configuration.environment { + ExecutionEnvironment::Production => Some(self.get_immutable_cache_provider().await?), + _ => None, + }; + let digester = + Car::new(immutable_digester_cache, self.get_logger().await?); + + Ok(Arc::new(digester)) */ + } + + /// Transaction parser. + pub async fn get_transaction_parser(&mut self) -> Result> { + if self.transaction_parser.is_none() { + self.transaction_parser = Some(self.build_transaction_parser().await?); + } + + Ok(self.transaction_parser.as_ref().cloned().unwrap()) + } + async fn build_immutable_digester(&mut self) -> Result> { let immutable_digester_cache = match self.configuration.environment { ExecutionEnvironment::Production => Some(self.get_immutable_cache_provider().await?), @@ -980,7 +1006,11 @@ impl DependenciesBuilder { &self.configuration.db_directory, self.get_logger().await?, )); - let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::default()); + let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( + self.get_transaction_parser().await?, + &self.configuration.db_directory, + self.get_logger().await?, + )); let signable_builder_service = Arc::new(MithrilSignableBuilderService::new( mithril_stake_distribution_builder, immutable_signable_builder, @@ -1116,6 +1146,7 @@ impl DependenciesBuilder { signed_entity_storer: self.get_signed_entity_storer().await?, signer_getter: self.get_signer_store().await?, message_service: self.get_message_service().await?, + cardano_transactions_parser: self.get_transaction_parser().await?, }; Ok(dependency_manager) diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index b6c9396d37d..97e57c38b05 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -3,6 +3,7 @@ use tokio::sync::RwLock; use mithril_common::{ api_version::APIVersionProvider, + cardano_transactions_parser::TransactionParser, certificate_chain::CertificateVerifier, chain_observer::ChainObserver, crypto_helper::ProtocolGenesisVerifier, @@ -82,6 +83,9 @@ pub struct DependencyContainer { /// Beacon provider service. pub beacon_provider: Arc, + /// Cardano transactions parser. + pub cardano_transactions_parser: Arc, + /// Immutable file observer service. pub immutable_file_observer: Arc, diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index a5351b3383f..169af5549b1 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -35,6 +35,7 @@ kes-summed-ed25519 = { version = "0.2.1", features = [ ] } nom = "7.1.3" pallas-network = { version = "0.21.0", optional = true } +pallas-traverse = { version = "0.21.0", optional = true } rand_chacha = "0.3.1" rand_core = "0.6.4" rayon = "1.8.0" @@ -94,7 +95,7 @@ default = [] full = ["random", "database", "fs", "test_tools"] random = ["rand_core/getrandom"] database = ["sqlite"] -fs = ["tokio/fs", "tokio/process", "pallas-network"] +fs = ["tokio/fs", "tokio/process", "pallas-network", "pallas-traverse"] # Portable feature avoids SIGILL crashes on CPUs not supporting Intel ADX instruction set when built on CPUs that support it portable = ["mithril-stm/portable"] diff --git a/mithril-common/src/cardano_transactions_parser.rs b/mithril-common/src/cardano_transactions_parser.rs new file mode 100644 index 00000000000..c5a120a8f3d --- /dev/null +++ b/mithril-common/src/cardano_transactions_parser.rs @@ -0,0 +1,265 @@ +//! The module used for parsing Cardano transactions + +use crate::{ + digesters::ImmutableFile, + entities::{Beacon, BlockNumber, CardanoTransaction, ImmutableFileNumber, TransactionHash}, + StdResult, +}; +use anyhow::Context; +use async_trait::async_trait; +use pallas_traverse::{ + probe::{block_era, Outcome}, + MultiEraBlock, +}; +use std::{cmp::min, fs, path::Path}; +use tokio::sync::RwLock; + +/// A parser that can read cardano transactions in a cardano database +/// +/// If you want to mock it using mockall: +/// ``` +/// mod test { +/// use anyhow::anyhow; +/// use async_trait::async_trait; +/// use mithril_common::cardano_transactions_parser::TransactionParser; +/// use mithril_common::entities::{Beacon, CardanoTransaction}; +/// use mithril_common::StdResult; +/// use mockall::mock; +/// use std::path::Path; +/// +/// mock! { +/// pub TransactionParserImpl { } +/// +/// #[async_trait] +/// impl TransactionParser for TransactionParserImpl { +/// async fn parse( +/// &self, +/// dirpath: &Path, +/// beacon: &Beacon, +/// ) -> StdResult>; +/// } +/// } +/// +/// #[test] +/// fn test_mock() { +/// let mut mock = MockTransactionParserImpl::new(); +/// mock.expect_parse().return_once(|_, _| { +/// Err(anyhow!("parse error")) +/// }); +/// } +/// } +/// ``` +#[async_trait] +pub trait TransactionParser: Sync + Send { + /// Parse the transactions + async fn parse(&self, dirpath: &Path, beacon: &Beacon) -> StdResult>; +} + +/// Dumb transaction parser +pub struct DumbTransactionParser { + transactions: RwLock>, +} + +impl DumbTransactionParser { + /// Factory + pub fn new(transactions: Vec) -> Self { + Self { + transactions: RwLock::new(transactions), + } + } + + /// Update transactions returned by `parse` + pub async fn update_transactions(&self, new_transactions: Vec) { + let mut transactions = self.transactions.write().await; + *transactions = new_transactions; + } +} + +#[async_trait] +impl TransactionParser for DumbTransactionParser { + async fn parse(&self, _dirpath: &Path, _beacon: &Beacon) -> StdResult> { + Ok(self.transactions.read().await.clone()) + } +} + +#[derive(Debug)] +struct Block { + pub block_number: BlockNumber, + pub immutable_file_number: ImmutableFileNumber, + pub transactions: Vec, +} + +impl Block { + fn try_convert( + multi_era_block: MultiEraBlock, + immutable_file_number: ImmutableFileNumber, + ) -> StdResult { + let mut transactions = Vec::new(); + for tx in &multi_era_block.txs() { + transactions.push(tx.hash().to_string()); + } + let block = Block { + block_number: multi_era_block.number(), + immutable_file_number, + transactions, + }; + + Ok(block) + } +} + +/// Cardano transaction parser +pub struct CardanoTransactionParser {} + +impl CardanoTransactionParser { + /// Factory + pub fn new() -> Self { + Self {} + } + + /// Read blocks from immutable file + fn read_blocks_from_immutable_file(immutable_file: &ImmutableFile) -> StdResult> { + let cbor = fs::read(&immutable_file.path).with_context(|| { + format!( + "CardanoTransactionParser could not read from immutable file: {:?}", + immutable_file.path + ) + })?; + + let mut blocks_start_byte_index: Vec<_> = (0..cbor.len()) + .filter(|&byte_index| { + let cbor_header_maybe = &cbor[byte_index..min(byte_index + 2, cbor.len())]; + match block_era(cbor_header_maybe) { + Outcome::Matched(_) | Outcome::EpochBoundary => true, + Outcome::Inconclusive => false, + } + }) + .collect(); + + blocks_start_byte_index.push(cbor.len() + 1); + + let mut blocks = Vec::new(); + let mut last_start_byte_index = 0; + for block_start_index in blocks_start_byte_index.into_iter().skip(1) { + let maybe_end_byte_index = min(block_start_index, cbor.len()); + if let Ok(multi_era_block) = + MultiEraBlock::decode(&cbor[last_start_byte_index..maybe_end_byte_index]) + { + let block = Block::try_convert(multi_era_block, immutable_file.number) + .with_context(|| { + format!( + "CardanoTransactionParser could not read data from block in immutable file: {:?}", + immutable_file.path + ) + })?; + blocks.push(block); + last_start_byte_index = block_start_index; + } + } + + Ok(blocks) + } +} + +impl Default for CardanoTransactionParser { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl TransactionParser for CardanoTransactionParser { + async fn parse(&self, dirpath: &Path, beacon: &Beacon) -> StdResult> { + let up_to_file_number = beacon.immutable_file_number; + let immutable_chunks = ImmutableFile::list_completed_in_dir(dirpath)? + .into_iter() + .filter(|f| f.number <= up_to_file_number && f.filename.contains("chunk")) + .collect::>(); + let mut transactions: Vec = vec![]; + + for immutable_file in &immutable_chunks { + let blocks = + Self::read_blocks_from_immutable_file(immutable_file).with_context(|| { + format!( + "CardanoTransactionParser could read blocks from immutable file: '{}'.", + immutable_file.path.display() + ) + })?; + let mut block_transactions = blocks + .into_iter() + .flat_map(|block| { + block + .transactions + .into_iter() + .map(move |transaction_hash| CardanoTransaction { + transaction_hash, + block_number: block.block_number, + immutable_file_number: block.immutable_file_number, + }) + }) + .collect::>(); + + transactions.append(&mut block_transactions); + } + + Ok(transactions) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + fn get_number_of_immutable_chunk_in_dir(dir: &Path) -> usize { + ImmutableFile::list_completed_in_dir(dir) + .unwrap() + .into_iter() + .map(|i| i.filename.contains("chunk")) + .len() + } + + #[tokio::test] + async fn test_parse_expected_number_of_transactions() { + // We known the number of transactions in those prebuilt immutables + let immutable_files = [("00000", 20usize), ("00001", 8), ("00002", 0)]; + let db_path = Path::new("../mithril-test-lab/test_data/immutable/"); + assert!(get_number_of_immutable_chunk_in_dir(db_path) >= 3); + + let beacon = Beacon { + immutable_file_number: 2, + ..Beacon::default() + }; + let tx_count: usize = immutable_files.iter().map(|(_, count)| *count).sum(); + let cardano_transaction_parser = CardanoTransactionParser::new(); + + let transactions = cardano_transaction_parser + .parse(db_path, &beacon) + .await + .unwrap(); + + assert_eq!(transactions.len(), tx_count); + } + + #[tokio::test] + async fn test_parse_up_to_given_beacon() { + // We known the number of transactions in those prebuilt immutables + let immutable_files = [("00000", 20usize)]; + let db_path = Path::new("../mithril-test-lab/test_data/immutable/"); + assert!(get_number_of_immutable_chunk_in_dir(db_path) >= 2); + + let beacon = Beacon { + immutable_file_number: 0, + ..Beacon::default() + }; + let tx_count: usize = immutable_files.iter().map(|(_, count)| *count).sum(); + let cardano_transaction_parser = CardanoTransactionParser::new(); + + let transactions = cardano_transaction_parser + .parse(db_path, &beacon) + .await + .unwrap(); + + assert_eq!(transactions.len(), tx_count); + } +} diff --git a/mithril-common/src/entities/cardano_transaction.rs b/mithril-common/src/entities/cardano_transaction.rs new file mode 100644 index 00000000000..066c413d5d3 --- /dev/null +++ b/mithril-common/src/entities/cardano_transaction.rs @@ -0,0 +1,20 @@ +use super::ImmutableFileNumber; + +/// TransactionHash is the unique identifier of a cardano transaction. +pub type TransactionHash = String; + +/// BlockNumber is the block number of a cardano transaction. +pub type BlockNumber = u64; + +#[derive(Debug, PartialEq, Clone)] +/// Cardano transaction representation +pub struct CardanoTransaction { + /// Unique hash of the transaction + pub transaction_hash: TransactionHash, + + /// Block number of the transaction + pub block_number: BlockNumber, + + /// Immutable file number of the transaction + pub immutable_file_number: ImmutableFileNumber, +} diff --git a/mithril-common/src/entities/mod.rs b/mithril-common/src/entities/mod.rs index ace21ae8681..f2d11bc785e 100644 --- a/mithril-common/src/entities/mod.rs +++ b/mithril-common/src/entities/mod.rs @@ -2,6 +2,7 @@ mod beacon; mod cardano_network; +mod cardano_transaction; mod cardano_transactions_commitment; mod certificate; mod certificate_metadata; @@ -21,6 +22,7 @@ mod type_alias; pub use beacon::{Beacon, BeaconComparison, BeaconComparisonError}; pub use cardano_network::CardanoNetwork; +pub use cardano_transaction::{BlockNumber, CardanoTransaction, TransactionHash}; pub use cardano_transactions_commitment::CardanoTransactionsCommitment; pub use certificate::{Certificate, CertificateSignature}; pub use certificate_metadata::{CertificateMetadata, StakeDistributionParty}; diff --git a/mithril-common/src/lib.rs b/mithril-common/src/lib.rs index 12fbb14f349..b227cb10af8 100644 --- a/mithril-common/src/lib.rs +++ b/mithril-common/src/lib.rs @@ -58,6 +58,9 @@ pub mod messages; pub mod protocol; pub mod signable_builder; +#[cfg(feature = "fs")] +pub mod cardano_transactions_parser; + #[cfg(feature = "database")] pub mod sqlite; #[cfg(feature = "database")] @@ -71,6 +74,11 @@ pub use beacon_provider::{BeaconProvider, BeaconProviderImpl}; pub use entities::{CardanoNetwork, MagicId}; +#[cfg(feature = "fs")] +pub use cardano_transactions_parser::{ + CardanoTransactionParser, DumbTransactionParser, TransactionParser, +}; + /// Generic error type pub type StdError = anyhow::Error; diff --git a/mithril-common/src/signable_builder/cardano_transactions.rs b/mithril-common/src/signable_builder/cardano_transactions.rs index 24f3ecf8ebb..21df1edd92f 100644 --- a/mithril-common/src/signable_builder/cardano_transactions.rs +++ b/mithril-common/src/signable_builder/cardano_transactions.rs @@ -1,23 +1,63 @@ +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + use async_trait::async_trait; +use slog::{debug, Logger}; use crate::{ + cardano_transactions_parser::TransactionParser, entities::{Beacon, ProtocolMessage, ProtocolMessagePartKey}, signable_builder::SignableBuilder, StdResult, }; /// A [CardanoTransactionsSignableBuilder] builder -#[derive(Default)] -pub struct CardanoTransactionsSignableBuilder {} +pub struct CardanoTransactionsSignableBuilder { + transaction_parser: Arc, + logger: Logger, + dirpath: PathBuf, +} + +impl CardanoTransactionsSignableBuilder { + /// Constructor + pub fn new( + transaction_parser: Arc, + dirpath: &Path, + logger: Logger, + ) -> Self { + Self { + transaction_parser, + logger, + dirpath: dirpath.to_owned(), + } + } +} #[async_trait] impl SignableBuilder for CardanoTransactionsSignableBuilder { // TODO: return a protocol message computed from the transactions when it's ready to be implemented async fn compute_protocol_message(&self, beacon: Beacon) -> StdResult { + debug!( + self.logger, + "Compute protocol message for CardanoTransactions at beacon: {beacon}" + ); + + let transactions = self + .transaction_parser + .parse(&self.dirpath, &beacon) + .await?; + debug!( + self.logger, + "Retrieved {} Cardano transactions at beacon: {beacon}", + transactions.len() + ); + let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part( ProtocolMessagePartKey::CardanoTransactionsMerkleRoot, - format!("{beacon}"), + format!("{beacon}-{}", transactions.len()), ); Ok(protocol_message) @@ -26,12 +66,28 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { #[cfg(test)] mod tests { + use crate::cardano_transactions_parser::DumbTransactionParser; + use super::*; + use slog::Drain; + + fn create_logger() -> slog::Logger { + let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter); + let drain = slog_term::CompactFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + slog::Logger::root(Arc::new(drain), slog::o!()) + } #[tokio::test] async fn test_compute_signable() { let beacon = Beacon::default(); - let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::default(); + let transactions_count = 0; + let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::new( + transaction_parser, + Path::new("/tmp"), + create_logger(), + ); let signable = cardano_transactions_signable_builder .compute_protocol_message(beacon.clone()) .await @@ -39,7 +95,7 @@ mod tests { let mut signable_expected = ProtocolMessage::new(); signable_expected.set_message_part( ProtocolMessagePartKey::CardanoTransactionsMerkleRoot, - format!("{beacon}"), + format!("{beacon}-{transactions_count}"), ); assert_eq!(signable_expected, signable); } diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index 4f72eb4de5c..1c6ab7f63b2 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -468,7 +468,7 @@ mod tests { StakeStore, StakeStorer, }, test_utils::{fake_data, MithrilFixtureBuilder}, - BeaconProvider, BeaconProviderImpl, CardanoNetwork, + BeaconProvider, BeaconProviderImpl, CardanoNetwork, DumbTransactionParser, }; use mockall::mock; use std::{ @@ -526,7 +526,12 @@ mod tests { )); let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); - let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::default()); + let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( + transaction_parser.clone(), + Path::new(""), + slog_scope::logger(), + )); let signable_builder_service = Arc::new(MithrilSignableBuilderService::new( mithril_stake_distribution_signable_builder, cardano_immutable_signable_builder, diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs index 94a7bb5feac..951d08ebebe 100644 --- a/mithril-signer/src/runtime/signer_services.rs +++ b/mithril-signer/src/runtime/signer_services.rs @@ -21,7 +21,7 @@ use mithril_common::{ }, sqlite::SqliteConnection, store::{adapter::SQLiteAdapter, StakeStore}, - BeaconProvider, BeaconProviderImpl, StdResult, + BeaconProvider, BeaconProviderImpl, DumbTransactionParser, StdResult, }; use crate::{ @@ -250,7 +250,12 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { )); let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); - let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::default()); + let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( + transaction_parser, + &self.config.db_directory, + slog_scope::logger(), + )); let signable_builder_service = Arc::new(MithrilSignableBuilderService::new( mithril_stake_distribution_signable_builder, cardano_immutable_snapshot_builder, diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 7cb63294b4d..1134f29028b 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -19,7 +19,7 @@ use mithril_common::{ MithrilSignableBuilderService, MithrilStakeDistributionSignableBuilder, }, store::{adapter::MemoryAdapter, StakeStore, StakeStorer}, - BeaconProvider, BeaconProviderImpl, StdError, + BeaconProvider, BeaconProviderImpl, DumbTransactionParser, StdError, }; use mithril_signer::{ @@ -157,12 +157,16 @@ impl StateMachineTester { )); let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); - let cardano_transactions_signable_builder = - Arc::new(CardanoTransactionsSignableBuilder::default()); + let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( + transaction_parser.clone(), + Path::new(""), + slog_scope::logger(), + )); let signable_builder_service = Arc::new(MithrilSignableBuilderService::new( mithril_stake_distribution_signable_builder, cardano_immutable_snapshot_builder, - cardano_transactions_signable_builder, + cardano_transactions_builder, )); let services = SignerServices { diff --git a/mithril-test-lab/test_data/README.md b/mithril-test-lab/test_data/README.md new file mode 100644 index 00000000000..293f88c584e --- /dev/null +++ b/mithril-test-lab/test_data/README.md @@ -0,0 +1,68 @@ +# Test immutable files + +> [!IMPORTANT] +> The directory name matters, it must be named `immutable` in order to be understood as an +> immutable db directory by our db parser (see ([immutable_file.rs](./../../mithril-common/src/digesters/immutable_file.rs))). + +> [!NOTE] +> The `.primary` and `.secondary` files are not real data. They're empty files that are +> here in order to simulate a real cardano immutable db structure. + +These files were generated manually from the blocks in the sibling folder: + +| Imput Block file | Number of transactions | Output immutable file | +|------------------|------------------------|-----------------------| +| [byron2.block](./blocks/byron2.block) | `2` | `00000.chunk` | +| [shelley1.block](./blocks/shelley1.block) | `4` | `00000.chunk` | +| [mary1.block](./blocks/mary1.block) | `14` | `00000.chunk` | +| [allegra1.block](./blocks/allegra1.block) | `3` | `00001.chunk` | +| [alonzo1.block](./blocks/alonzo1.block) | `5` | `00001.chunk` | + +The following code was used to generate the immutable files: +```rust +use std::io::Write; + +let immutable_files = vec![ + ( + "./00000.chunk", + vec![ + ( + include_str!("./blocks/byron2.block"), + 2usize, + ), + ( + include_str!("./blocks/shelley1.block"), + 4, + ), + ( + include_str!("./blocks/mary1.block"), + 14, + ), + ], + ), + ( + "./00001.chunk", + vec![ + ( + include_str!("./blocks/allegra1.block"), + 3, + ), + ( + include_str!("./blocks/alonzo1.block"), + 5, + ), + ], + ), +]; + +for immutable_file_definition in immutable_files { + let immutable_file_path = immutable_file_definition.0; + let mut immutable_file = fs::File::create(immutable_file_path).unwrap(); + + for (block_str, _tx_count) in immutable_file_definition.1.into_iter() { + let cbor = hex::decode(block_str).expect("invalid hex"); + immutable_file.write_all(&cbor).unwrap(); + } +} +``` + diff --git a/mithril-test-lab/test_data/blocks/allegra1.block b/mithril-test-lab/test_data/blocks/allegra1.block new file mode 100644 index 00000000000..a276b1f3af3 --- /dev/null +++ b/mithril-test-lab/test_data/blocks/allegra1.block @@ -0,0 +1 @@ +820384828f1a004f3c641a011e1523582008d4d3e0caf55d66233e1e3421f97ea7423c2a82666e22ae216e464ff431a85058203593d74edc6c343c9e9b9d329f8356aedda414992f2f97c2bef93cf606b7b87a58205cd4a875fd1317f8176a910c6705cfdc80308d031c9a3fa6b1bc2b4b4d5621468258407b7d4c4bb23bf66c8aca7ce4830b9f1cadaee5061f89fa57995a8d07ec59f126730368e2b03c754b143a38a31c7a39ca830fb6de287129a27496a48602d39df058500c8cdf0ccba508d39bb915018c420dd057cd5d94fd2cf17b1c3a5f4522581dbdecc54e9de4d1e61d12b4d09db5a5be92b145d1b63e563ee8039af378b69b2e81669daca047d5e34750c34542e3503f0c825840000607436ed04d36164ca57f0f70bfe10ddc3f276540750dff3c0ca399047292c3355e4a488d0f13e548134661056f434f4c966c52f20c6580252c8a25601ded5850cb50509a5659569e8a1881d77dc335a781b245a14a499a7c04cdb9f6ee04be3ab3bf096c42a6c817198477bd2f10a426f7a7cf70d579d2238fecc3ca4597f8793ca02b02574ceb45fe9892997509ed0f1908ae5820dc6af41ca2a4eeb3d2969e559ddb65d2672f1b9aaf901fe4af425407952e290958209d0d3abaefdf5a2ccad0832a80759b8f8eb3c0629e9dc8bda3d44ebf658ab97601188d584051977ef95fbb458fa81ddc7e1ca6345e3f5fc70ae5b80b47d95fd39ed2940d1751ca7ce6dd24901265c299bf1994b3830ca10e995d7fde4beab807cbce03470604005901c0750da95fc1de2debd8cbde31d7f658bb41170695319a9468cde8cf21a93124f7db032571299748da724cbfda88d5f79fd89ac2b3fbd0748e25129ec2e74da20f96364f691ca551f8a7e71305d225a810783947759bf28d580a51244029c290e7c27e1d249f52ed39ac65f66529240ab76e36a4001df5a24b971557475379b24a695a9529646eeb7f1681a3d215fd6fe550654a1e7b06089b68f525b0aba6d91adf229eef5af4a3d602c1d29ba5f18c8a93bd6c950134f37ccf0ebf1b78fa2909f98ed560220de39302f56453b979da7843f6b0cacbe1a33739aa272f193bf9de69d083c2c885c1e226c0e0a35a2400059cead716cfa8064fb98f20162b2b3586c5dd29ac557ad542c610dd6576669823c757353fd03208313d8715268b32ef8ab62e02edaad3ea9c96de888d5933ccfc13e757f92bba98b23a5deb1cb860550f272f685a5bb9b88e2f6bbf227c049f90aea4540347bdac041b09bb75c83352fcea02f91c648156aeada5877eb3adba690c073a6fc1372a48ce8115ba3537243938ce366078d07c2f9dfdd92db07e0047b8d78d32fb4b55fec8810fd9d128c88df3c5d435dc82e163f9275dac718d06f680a3be8db4dc8dd512b059b1631191f283a500818258201e8f5d41ab9915c4cb0fcf1c4127e18585d957ac95b2b4879c3e02b02205cdcf00018182583901495a5a4dd65a483a5eaf6630f59a2c33e02e2c15d1d1bc39d1708ac457fd410378da5864e6b313db03488a085ca56376af0ab5e9854376401a3b76264b021a00029eb9031a011e324b05a1581de157fd410378da5864e6b313db03488a085ca56376af0ab5e9854376401a0068d5dba400848258201f15d61fa4462dffdb330d625b1fcbec930ea633b7bff5fce23e969660702ef500825820cc59f188f8e4027e98fa33404a7ce69b76e0c6b211f11ad71b4daacf67906ec900825820da82abf561c83cffd8657c05d27244081739eacb880b3b5b4db03b1a411911fa0d825820e125166ebe2e0b7bc781b9e7aa886a032ec87cb40e0bdc1acb5058a0555a8f82000188825839015fa4a029126ea6679f6cf29d15369dde7206472bf0f9d63e12a88f575fa4a029126ea6679f6cf29d15369dde7206472bf0f9d63e12a88f571b000000019fbcb30c82583901785eb7eb6c8286b8011a0adbf3ca1b4d37589b80cc6e42fdb4679adc39bc6cf63c0f9dd4f03df80accce7246bea60d649de4a8c4d5c0dca41a16d9fbfb82584c82d818584283581cacf4df690f13b4eaf82b816f399b2e1d8ab86976da93009b813d7758a101581e581cca3e553c9c63c5c2eef54d43a2e23449490865698bb72b3b1c039082001afa0dcc441b00000067e78e120582582b82d818582183581c62d2399c601895246274b8068266fa3138fd09f79fe636aa432db929a0001a5d1abc701a1263562082584c82d818584283581c3cc42d469376e7a5cfba6823160d341b5867c66d009124bd000bb109a101581e581cca3e553c9c63c5e89765f8436cd338c66da66d119ab3c27738826333001a65307a281b00000004dc9ffb9982584c82d818584283581c05bd51fb87d91b5290ecf18958de205badec391e4d643740edb2f0dfa101581e581c735437123ef5c61d0591c67ef0c94a5dc5c18653d6c629949595edef001adf8508d81a2c932a0382584c82d818584283581c5acbd13c916ce291ac4f41b1df2c4c75b8e917622040b54e3376e42da101581e581cca3e553c9c63c5dd0255704321b81423e0ffb4018d237e8e9a96ce3e001af6cde4f81b000000095b4f070082584c82d818584283581c8c8a6f436c8b04fe1c682aa81d417f2e82b41ebb06bf9aeb8a65a198a101581e581cca3e553c9c63c5b7f725d5432632ff88fecdf5936a00291efb732202001a1ec13f971b0000003452024d88021a00036a91031a011e30d9a5008182582026571cc2e731014dfee8536a0be0b816cc38b10ce38501db0e1a5eec4f04ae860001818258390192da16a658a4ecdb2c18b01e464f4ce2924f8830a304648674c1c2756162cdee35822637a84e896c6a8b05214cf21947f91b69122eb985891a00495250021a00061a1c031a011e3123048183028200581c6162cdee35822637a84e896c6a8b05214cf21947f91b69122eb98589581cae66e56ab11ccb39e882669f220a37956c683e4ce84fefd910012d7a83a100828258205f6f43cb12b59a3e5bf242528c8ab85e4feeb2acb89aa08e4948968f7bf2d9e0584010d19b492320347d5b6adf2137ec89f9c13d3fc07cc9b32f86a71fe001fcb75f36e3958949ef2b7e37377d89246b4a39463846402fa870f81d1845ef7c5c1d04825820f49ae225837477663687484ce73ac607ab7fe78600ca08d61db09e2e35d8e1b6584096c80781bd34262ab6f7b12f6592f0b943021e97e9c70ffc23635272c742932a51e4a252341c47a79f4e698235ab22beb3a75256b6476fd46749a68c6e86f60fa102848458200915adaebbd25d0f9a3242bb83c14bf5b012967ab8462acdee1912062a76117f58407a9ebc40eefdbcb1586e9805043ccc3285ee3864686fb1fb0d6d8075a41abd026864d0e92224982acfef061da2765effb26c75faa62c5ef3b656386782987c065820aa35c675609419a21a79ed83318278cbe669d4a4dadbfdcd6234e1315ba7c9685822a101581e581cca3e553c9c63c5a59118f7438117a8d6603837bc193303b5001b095b8458202f84330afbe6ab81c368ab0a850e8b6fa02daacc91709faf6bbdc811b4b5f82e5840bb878822cb198b1d245e2c46c20c930996067fbcb3469a8d36b68d6531add3e3e5081200890d906c70b1bd9e4333115a7c17df8da18eb19148a88a431993900f5820e91eab0e2dae5b26ff019c4b1d56183bfd8f5abd1fd258e4f58b13d55cba36f55822a101581e581cca3e553c9c63c5c0e20ac0433519070b0d32e51dbf04b191a57d44b3845820894b8d96727c3d68a30a7165c67c9bff346260628ee624268dc35d660e55794b584062b68793ff855c0b11a0c0e147de91a9f0631c565d28d270fdc146571d0c23839c970996188bf4e86b7a2d529e694b4301de1692366349913b4966f06be9df01582033aacbefeda60713a90163575735e391b5a1a00fe8629e52875657ed5c93ecba5822a101581e581cca3e553c9c63c58681fbd14353af0a213e5ab85001e4e2cbad09ec518458208e2c6fc08234b2a3437e5bae310f6957c69a62143318c562bc1b72c85141df6d58402f02c6375f5074b287aa72e940a3280f39d6fd2c24240b087f208fb92cb2abaf911657eb06b6e24756dea333628972c611da12aad9cc8dfa6b15a41f1e4bb70258201d6ed1791e5bb28ead7bcc582d7e5e70d264cf5293222352597bf0c2a773e9925822a101581e581cca3e553c9c63c5f777eddd432f2063462cf96a64e586148173444979a10082825820036dc0c351864d35c3d8fb72a0acbd0efb343e005f457b0a845e3a82a48bdaee5840ef6eaf7713acfa2623a589e6a5764f93fff7bed4d60d549a0e47d985bcccdba34e79b6954768b423db879e99be2ea205a151974072734fa996d240dd876bf600825820f2acaa3d79a96db3f8cfbf87792b62e39fa74a4d9ba1bcb82ba1562ca524c0a158406da704b6ca04547fd8fda632d1acdc1f15c5efaaee4e1d92f35c714b59148511828870dce8602de968da15263e938c6f71b37e6db9b47fa217661efc1c117b01a0 \ No newline at end of file diff --git a/mithril-test-lab/test_data/blocks/alonzo1.block b/mithril-test-lab/test_data/blocks/alonzo1.block new file mode 100644 index 00000000000..bc635929264 --- /dev/null +++ b/mithril-test-lab/test_data/blocks/alonzo1.block @@ -0,0 +1 @@ +820585828f1a002f48941a0295f18a58206fe2f80eb8cf6ad02d3a3857391b05aef41e575486168d7c36ab693be24c76f25820e7c3155586042372b19c1fe0491b771bfb2eb04f24af76f3870cda983551f4e75820d8ae2a59f1ff6ec33d0df8161fd89d820533b9580a4e43f4e9f6a628582b10ae8258402b498e5bd3f73130e1b7e5ac199fac1a688d948d73d71ec7a951913a6543131c44dcdba5215341b1dc2581c096e99fcf5885f42a9cdcf476322b38cc837111b858508f08dc2bc1e3c6c8a3e6f7f5f1d7af8d2ad33c4d31022f4777641a946938ba0c3af0f78076cae00ab741f4d39dec2be431710acfe55d2b3f5868b24847506b77fb42d639e95bdd025f5f406abec9240f8258400022f7e10e560aad60a6f16b743ff04b4abcd4ba9b572e02c67bde6defd29290a9345b3c1b28ee1dfdadf631a3887bc21807ec5bcadeb6a495f3cde7cfc0b1085850e6db5933067747401ea665a7d8fdb5a0ae131bfa757aa07e2b3fa619e0d94245233806ebf7340826f076f6ff62fe4600e427643ed77e3f02be7c39370c7d46f266e4f5a23c2cee05735e2152b1a73c0e1906965820c48e87eaae5983daca6d2611e5b45a09c4a8300ed2e36f747058761b976c2303582060ffa1e3c1ab6d03a5447d2f40ab023dbce45b13f0e372d63a964d31c7ee60790619014858405915c6868aa7c19b007464392dd4878f710c033e83d1421e188a993dc13a66c3bd60454228eb8105a3d37509ef0302633e42d4b20f86003a83b25a87b8b1ae0c06005901c0eac3f1484e8d6278c81251ce80767faa39153ae2c509795065f6859d87e5ca60356f2e0528e26441091d5fe855b430659f8c113b8e090ac7b5dc2a3f55811b09ff956db28c653766bcd95ca7ed09a8e0c744b75c4cac7b48561ad922978a866bb9014ce731cf098a346d58fd9602b5c712c587cb4ad2d31fc3c869b1d68fa3eb53c94453e0fa42c15686ace90df691b14f1372eb7e86897c0f22d26ae043b105978b6652d4144c7a3c5ef9b61e7d46403acbbf158075e31e4d45969ec968b62a27c05f4c2c8448da438d9b2b0f98a6df9245326476310ac26b164ff0b40e3b7e1f05c38d227f5b9ab87d82c7b64af3351a636ccdb951b6445f7909e56507f301b267d9780335863b3ed7d3ba16ebca3e9b77bc3ddee15436279b3a33eca8dc66a87b2864a550b003dc43622aab8183e891780ab8cd56fcd4ae28775ad6b69a786e19fca6362905d65d92f3b59f8259c1e1f52a8fe125d29c3dda7fcd45c7c71eff039986ce812e9a1f66f6795f53857ac57d32901f6a1992e42e7c8d7a942d25f77f46beb8e4cf801c80372c958face6b1a9dfacc38182310d66ad4816a08b329451c088889a2cb62fedac1944addffaaa0a8dc5cb54b2974b70411226918b8285a40081825820bf1f12a83095ac6738ecce5e3e540ad2cff160c46af9137eb6dc0b971f0ac5de000181825839009493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e32c728d3861e164cab28cb8f006448139c8f1740ffb8e7aa9e5232dc1b0000000450b745b6021a0002aeb5075820f607381cf971f3ab1119ad680f73bcc66c8d8d30136afbf82fe05f44f7924487a40081825820df4ebe9ac3ad31a55a06f3e51ca0dbaa947aaf25857ab3a12fe9315cabec11d30001818258390076b0d16f5d09ac02dd1786981066f6fedf7ac165a08b3b6f0fb33f039bf76872ce2fc9debc7c431ce4700ea060a5aefdec0a173d8ed6b4261a02fe430f021a00029d590758200013eb4278b47fcc6298f02bd42b31c919cef864dd20a9929d5eee315e13a557a60081825820087138a5596168650835c8c00f488e167e869bd991ef0683d2dbf3696b0e6650010d80018282581d6095ca37fc71b3b73f2d0e57258ac66857661f33a509436c10917aaf6b1a0022e4be825839005790dbdd97b76eb273e290122d6edb7504ed392c7ea7b011bd25d936719b6905b4122f96283e50a0dad9a9e577553ad49a17bbec3cb715181a2deefb98021a00028f6d031a02960d5d0e80a80081825820cc9f28625de0b5b9bbe8f61c9332bfda2c987162f85d2e42e437666c27826573000d80018182583900be8bb38da7b499acdb1eac0c05dd2649c8de5d791ac87969903df470244805c91110d844e9f4a776a5f201c71313358c13caaa3ee7b488e6821a00bade29a1581c6d566ad1e649b6e86a2f4fa16a4cdf99616230b78742332cbabc5fa4a140183b021a0002c959031a0296183b08000e8009a1581c6d566ad1e649b6e86a2f4fa16a4cdf99616230b78742332cbabc5fa4a14020a30081825820d0965859ce9b3025ccbe64f24e3cb30f7400252eb3e235c3604986c2fdd755db010182825839000e87d178321157275dd6a10f9c40ead38a78ae4703a23a23be57f1cad723da7bf8787357a74b3fa486b13462578ecf6ab6774dd4f15043681aa0eebb0082581d607c4a71a51d0c7400a15a748fa2338f20c8386f1ed4ebc56c2ffec4b01a02f59afe021a0002ad2985a1008182582073fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d5840abe07f7afebbcfd1816e2f70b66cbe2a0be6a46db86fa783949e4319e203e8faed12930489841f287dde09d00c5d1ed15bc4d47ba1e1deba2bf59350fcadec0fa100818258209dd11e64a93710854fedf85f1b4b23192079b812b67ac3ee0b97bdb11113806158404cf3a0c85491d39ef8ef91dc5b34107da610376301b53ebb2a7ce7c2b8397f6871890f1bf875e8f964146dd7107ddb89b08c343e0d4561481ff5b9e9b7f3ec09a100818258200530deb7fd4edb6b32e312806c7231d3c507f53fc27d5894c14a454936b16a435840369d46b7c720cbd253327c45861c0be0c741babd4b82a29c42c531fbc87562e40496934689289162082eeefe583e3dcd89aeeda671457d30bc21e5f9ba2b8e07a200818258208b5a5251b8a3f1f1b76e38377b3cf857d38284cfa48a2eee46c93df89221e0275840cfc689b9ab1ebc2dcec3f27fba6448b610b4bbbe46ec430c7e9e86ab0b454ca9b3b08efb6e843783f860512b8f2aaa142073766a7fb43ab6ca7ac5ac426f680f018182018282051a02b5a9468200581cbe8bb38da7b499acdb1eac0c05dd2649c8de5d791ac87969903df470a10081825820a87e24f3f590c98c9bc1f34e11607db0e04446afb3b02f3201b8a6972e50512d584018f04cd87d600d8fbd03d1292e4e16cd3ee4f8fc3cc6e504216373e1a8bf8826e17e365931e988e025b2345a390e9e6bbd78f0b0abcb24e0d238df7592570b07a200a11907c1a56641444142544381a266736f7572636569636f696e4765636b6f6576616c75656a302e30303030323937326641444145555281a266736f7572636569636f696e4765636b6f6576616c756563312e35664144414a505981a266736f7572636569636f696e4765636b6f6576616c7565663139332e39386641444155534481a266736f7572636569636f696e4765636b6f6576616c756564312e3639674254434449464681a266736f75726365667472657a6f726576616c75657132323637343134383233333435332e313101a11907c1a363636f3281a266736f75726365664d485a3031396576616c7565633438306868756d696469747981a266736f757263656553485433316576616c75656439362e396b74656d706572617475726581a266736f757263656553485433316576616c756563392e3480 \ No newline at end of file diff --git a/mithril-test-lab/test_data/blocks/byron2.block b/mithril-test-lab/test_data/blocks/byron2.block new file mode 100644 index 00000000000..2c26a9a3ac8 --- /dev/null +++ b/mithril-test-lab/test_data/blocks/byron2.block @@ -0,0 +1 @@ +820183851a2d964a0958208b5add21497fa1749292318f1ac96a252ee58ac9bf60d321d1273f5135e72f2f8483025820eba8b3720ab760171e4900b88cc0379bf6c78c4dac55d1dc3d646b00c13515405820187817e2ba9ff807dd905e3a4e0d061630979da49071495b2b38201f7ffbc97b8300582025777aca9e4a73d48fc73b4f961d345b06d4a6f349cb7916570d35537d53479f5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e40988482189619056558400bdb1f5ef3d994037593f2266255f134a564658bb2df814b3b9cefb96da34fa9c888591c85b770fd36726d5f3d991c668828affc7bbe0872fd699136e664d9d8811a00316fa2820282840058400bdb1f5ef3d994037593f2266255f134a564658bb2df814b3b9cefb96da34fa9c888591c85b770fd36726d5f3d991c668828affc7bbe0872fd699136e664d9d858405fddeedade2714d6db2f9e1104743d2d8d818ecddc306e176108db14caadd441b457d5840c60f8840b99c8f78c290ae229d4f8431e678ba7a545c35607b94ddb5840552741f728196e62f218047b944b24ce4d374300d04b9b281426f55aa000d53ded66989ad5ea0908e6ff6492001ff18ece6c7040a934060759e9ae09863bf20358400e663202ff860e5a1cc84f32ad8ceffb0adb6cf476b07829e922312e038ba23573728e02f3775f6714b3b731f8b8084d92b3f38f51a41ba859e7e700feeeab038483000200826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849f82839f8200d8185824825820da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d50eff9f8282d818584283581cdac5d9464c2140aeb0e3b6d69f0657e61f51e0c259fe19681ed268e8a101581e581c2b5a44277e3543c08eae5d9d9d1146f43ba009fea6e285334f2549be001ae69c4d201b0000000172a84e408282d818584283581c2b8e5e0cb6495ec275872d1340b0581613b04a49a3c6f2f760ecaf95a101581e581cca3e553c9c63c5b66689e943ce7dad7d560ae84d7c2eaf21611c024c001ad27c159a1b00000003355d95efffa0818200d8185885825840888cdf85991d85f2023423ba4c80d41570ebf1fc878c9f5731df1d20c64aecf3e8aa2bbafc9beba8ef33acb4d7e199b445229085718fba83b7f86ab6a3bcf782584063e34cf5fa6d8c0288630437fa5e151d93907e826e66ba273145e3ee712930b6f446ff81cb91d7f0cb4ceccd0466ba9ab14448d7eab9fc480a122324bd80170e82839f8200d8185824825820e059de2179400cd7e81ddb6683c0136c9d68119ff3a27a472ad2d98e2f1fbc9c038200d8185824825820adeb5745e6dba2c05a98f0ad9162b947f1484e998b8b3335f98213e0c67f426e008200d8185824825820f0fb258a6e741a02ae91b8dc7fe340b9e5b601a6048bf2a0c205f9cc6f51768d018200d8185824825820c2e4e1f1d8217724b76d979166b16cb0cf5cd6506f70f48c618a085b10460c44028200d8185824825820aaca2f41f4a17fe464481c69f1220a7bfd93b1a6854f52006094271204e7df7c008200d818582482582089185f2daf9ea3bdfdb5d1fef7eced7e890cb89b8821275c0bf0973be08c4ee901ff9f8282d818582183581cb8340f839cc48449e2ee6085bf1ab6f152fb20d2e071f429085bbe13a0001a5bf6bee11b00000011972734888282d818584283581c7ed455da1e6e026b35204699d4fa39b3bc9dc47ed27d36d2b47ec3eca101581e581c082dcccc3e51655ddc77c01953f7b64c7cf6aca758c686d9c51a32fe001ab116b6251b00000005bd6bbf308282d818582183581cc2a3e4bb135f14d44b5659fd3cac5f950e7515f8ce243f264dfe1befa0001ad2e633181a84dbd7408282d818584283581ca492c5ad6316f8e7e84177673acf32f53ec25b2baee5a52ff50964f9a101581e581c1e9a0361bdc37dd4b96893a363943b3fa8e2e89b8761c3f0ff7568bc001a600064881b0000008cc4df8c118282d818584283581cf73d2b9c66ae7c8d479151c7e43e2fae59fba02e7108ac4d643a4909a101581e581c1e9a0361bdc37de543987ea3cadcec1c18fa6cf784794fec37146f34001a7103195d1b00000004f92e889b8282d818584283581cbd765f3097754bda870653533855012a82a22cedf508afc97a05f912a101581e581c1e9a0361bdc37d9d5470dca3256d2b36da565a1be6fd6c319321d8c9001a743764aa1b00000011b52f1056ffa0868200d8185885825840b43ac73cca84eb7baa30f0ad3c4d427ea43e8d8831eb8de34ae9994bc6f00313d81f35d51d695e30f88b267572f96f056ddff1b39acf011b8227c14df833371258408a0c1d59791fd04e6329adcf532fe2749a8da282ca4bdba9942a6acea07b3e4d9d30cc0dcd9a602e151694a417d069fdaa8a3bbbb2e1724b9c9671850498430a8200d81858858258408a3c3f38a7a598bd4b62fa4b56f92b96be3d1b7f1df1a713a3cb4978f835396f935c09b245ab312efda634d3ac4af1f843fb0577725a27579c9fd665d14c24fa5840a27f622daaf26f1047845723f20a13247ce866005f5263397082657a80d60095d81856e127e27e2fe36391c1631170ac630d3e340aed71c9cb8c323eec148a058200d81858858258409322bcf07526e852e5a0830d7c24d4a2a0dcf8a894f176e0a8fe3f742fa485906206283cdab4fdba8280fdc51e818ccdd1e9e6cd530381b18152f38dc598b6cc5840d0edd562447c433cc7870913fb8d90a2169d4a1f9aae8695eedf07e0eff67213fd7c339f03860ecd83ceed1a324aa10067671e4ee1d3f762f355678dd4d0ff008200d8185885825840ac4a10ea86a183dfc8ab8f9ccf7a61eb9b11e471b53321725bafe403bd101abdfac85680f85d72f5462b4abf234759f3d833f7fe5372884f8a84475f4989edbd5840181183c6c147ba34c9f39269ab36949d0b1ec0a7dd950afa1ea2dd27b2d691412948de68f93471935905edf07934a7965926f52f8162841299a55246a5a97d0b8200d818588582584004bff1f0caaf06b7942f52218c1b8a1583f425014ec78ff2c4030f90dfe191ec861f537833d47898185d34746877e9d9d31cb827c145729d1200c1463625b22058401ffcb034120b9b20d84af9baec86bb0b2593d804e8b544d7dcde662b0e2a3da4b4afda77007a4133a9e0ea5306a92edff4fd82a14db371407e6a67bbc644250b8200d81858858258408a3c3f38a7a598bd4b62fa4b56f92b96be3d1b7f1df1a713a3cb4978f835396f935c09b245ab312efda634d3ac4af1f843fb0577725a27579c9fd665d14c24fa5840a27f622daaf26f1047845723f20a13247ce866005f5263397082657a80d60095d81856e127e27e2fe36391c1631170ac630d3e340aed71c9cb8c323eec148a05ff8300d9010280d90102809fff82809fff81a0 \ No newline at end of file diff --git a/mithril-test-lab/test_data/blocks/mary1.block b/mithril-test-lab/test_data/blocks/mary1.block new file mode 100644 index 00000000000..7d71682e34a --- /dev/null +++ b/mithril-test-lab/test_data/blocks/mary1.block @@ -0,0 +1 @@ +820484828f1a0055b4ac1a01a1eabe5820e9aac7ed907b246dda5548cf8d78269ddc5006368eac3d6d953800e4531ba7855820cca475e401893077582e74e5f694e741e5ec78df16f5ca4f979a2f297d02c54e58202a20b39a975102c19ea1d145df120579a2595ce6d696753424b13eeceb157fdf825840ec7d0f21df996a302d64f5700d24c11595d95cf30e755295575d9a33bdc9e0a49f777c501e0018161595b8d417e2ef3643f0315dc5f1ff63a2806a7c5c635eb85850aeba5ff94a58858ba358c540ec6fc7cccf4e55af1b45ea0b6f67a20d5def696d3d42cfb1571fe6b282bba6480d407b933d90228f4d9a5a1aa1a7da41e4464e5a9163c8a4ea5b1c95f41dffd9b905680982584000063af76ceade8a17009a28ae666fc90d59b42e0c0a37d4f4537755a66bf2b76d8b7dac1b8bba2b09a5784ca6878d95cee62b6e9987cf3c8d7d22f71526313f5850e584c25e328ce17b61038b33295e1317bf1b0b6b193a9e6f9d9ab455047dd6b5163db4d0a54de492e61a5fc36757880556369522326b2439e14ccc7bdf78eee3f0edae0d2900c38910a0d64d8f8d6509194c4958207cdc18ac07604d453229464652123b6b51c137abe9279e2eef9c0621d504423d58206dc1e500ee69ee4f02d67254ca3fb8ede4c592f4dc145ba4a9a001225913ad5d0418af58407d82457cde034555f832f7a2e01623b0b5f519d54b643e87add08cd6192a161a05dd0da2979d9b547ffcff442372d30e1df19e3c2e5179e4a04de4931fc1430204005901c0b37b5e3b093ef3082bdd548bd5f931f6621a2eca32a6358b38a42a296f2bd4c5600839e7e504fa340812e45337065007fcd5e1e41e1c4ecbec52c33895d187034b970683ab069478692cf6b98fcac79530c2c9bfe2295203e228a8809ba3f9208084a984e5588274ed0a26c6942ef729df8faaf644f7173c68ab986bfdc20f6108d7b3586c7bca66c5e9caedb0b021a23f2131b8f37a030be68e56d52761d8be70493151d0c6c1905e33701b8e7d1ada6a08e4fb410c8c5fda62fccba93d5da1d01477969f12ed67e981961b1a3cbd6cf981bdd593ff28fdb753a5f11ceb62621dc2e87472def58d90742c8a26b5b64573cbbfd32d317b3d77e08c3883f75b148af2784e0c1b5cc84b5b3266cab7bda13c3a893e05ef85d8b063ad239aca2c6018c10ce411aa8bd96abb0be37dd3b629d4aa4ddcded309a6a4cf8e1771aa97b943ffe352719145859c78e07409ff8893802bc1e492486566892c5c48b508423aee0890b940bab42014f9f6d29165a1fb8b53098f995a45e112a0bc66d3d9ce11ddb31739878295fe07db1e66b425cc52a5d73e9088ac1cedf6f3cb832b5fba8ef878cfb5e61813d2d56c6219ef54e2ce0f29c6ffae6f4a776a2ddc70a08be9d58ea50081825820790feaca64576990e3b2ed9cafdb8d2b874c561019f767827e685164577974b30001818258390111aca4f4fe84f5d89a30e8bd1697b52d0620c6b8e2a243c07e226f58f2971be702006ff49954fb5c064347451746ffb7e619bd2774caf59c1a3b7ca69f021a0002ac21031a01a2068e048282008200581cf2971be702006ff49954fb5c064347451746ffb7e619bd2774caf59c83028200581cf2971be702006ff49954fb5c064347451746ffb7e619bd2774caf59c581c024dcb42f0aa6d81a7e26ccdd525a2ed3e9665d126b38ba0f8b77b50a400828258205ed7d5280841f21b89d7d4885e0f4a3fec1d0d05856b68735ed5982677414a9603825820792c27f11f603e2a580847d42f4efc35c23492ae44ae3fd4a5f0deb7bcd8143803018482583901105d5846c4d6054a032092ab231ced9d9ec1cd381c685ecdb15a18eb105d5846c4d6054a032092ab231ced9d9ec1cd381c685ecdb15a18eb1a05ffd451825839015a06e23444ea5af6480d86f01d593cd2c061a1002db515a36df3fbf05a06e23444ea5af6480d86f01d593cd2c061a1002db515a36df3fbf01a126cab0282584c82d818584283581c4bb5beb1813d875ef62a2d8fc47674812a361f1834994e9cd5cbf0eda101581e581c9b1771bd305e4a22633e79a9257cf2458a580a65a4647d834268213d001ae8f33c161a0c66eca582584c82d818584283581c2a224ea8eb307e51dc1c9d3287dd5b73fff36ed2f318d34e4fe73204a101581e581c9b1771bd305e4a615d497aa98e87cc5b8bc918de283f0d9fc265d865001a4954b2701a25f6df28021a0002e715031a01a206c4a40082825820b34f463a8a91f91c207cf3148cfb253b2795eea8643b89281fb1bd043bf35cc700825820911ee5b314df7fa744ea47231bc42ddf3bcee2c910e19fdc3ecfa087912e78a101018282581d613dc0992a980257fbf7e6ae1e9b43af62932885f1e119279f1ce43f511a1a18ff2782581d61d91ef01b73f3010bb173945cf5417257c00c002715a13052015ab54f1a08655352021a0002bf35031a01a1ee84a40082825820861d31b11b125f78a88aea42a81a5463cec8bba58f6671227436822cf79506970082582017949570550c96e1d58abb5a4f07861ebde9472de865e56f0bdf04ba5165e86d00018282584c82d818584283581cbdcd97d67a94fc9e1af08ba34fd47405631a62362482b5c63aefaedda101581e581c7111304fa69dc398fa2055a1b012921e540374ca44f739beb09131c8001ab1e823421a03ea796c82581d610237be10f5ec0ccb6cbd226b112f0940fed44ae0466d9b53962ba8b11a2381e19a021a0002bf35031a01a1ee84a50082825820f74dccfef00ea5dd7a5360f4870de9f664bcbe8c0bf4611ba4d8f5dec687cc9f00825820f74dccfef00ea5dd7a5360f4870de9f664bcbe8c0bf4611ba4d8f5dec687cc9f010182825839012250f08ab10f7bf12f49291e78527f35a4f66ebd03e66524ed9ac8dd2250f08ab10f7bf12f49291e78527f35a4f66ebd03e66524ed9ac8dd1a000f4240825839012250f08ab10f7bf12f49291e78527f35a4f66ebd03e66524ed9ac8dd2250f08ab10f7bf12f49291e78527f35a4f66ebd03e66524ed9ac8dd1a85e84858021a0003096c031a05f5e100048182018200581c2250f08ab10f7bf12f49291e78527f35a4f66ebd03e66524ed9ac8dda40081825820900c833fd9754d4f65d7a1493ce62aaa943c887dc39921d0975976896bbd4e8a00018282581d61dfb778ad19859d22c8909f541b570dd4c386efb78d72173e4af2d2481a04c4b40082583901e683340376e5d57ab9af32493fc8f40c21682621432ebc85ffb4af4eee0df6c2ebfe5d3648def953480b811698b54c9a38164dcf0bf6c0eb1a2ee6c16d021a00028c81031a01a20663a40083825820750a3eab714b04bc4718f43407fee903b996700aa3b0e171f840602b608ce52e00825820d57e3d99a481cdc928a82d2a39b689f49a54044c6cd12239a8e5b2376536659b008258204316395f1f1421863c5faa3590d5cfa91eab9f8c5737afd0e6df8914b8ca5b5700018282583901b46bdb5502435a418fcc35273339c218300e0d633e4c532d98133907cb970842e16c849f65ffb4c0d88243289cf7519aa90bc57b3c3597a0821a00160a5ba1581c73ed4b685261a41c66ba62193572c3ce78652793df0a2d4a75c127cda144574545440182583901306fc9574d0f8c774fa8fde9b5d540e2d4f22c67e1ccdb862f8bc6d1f68419a027bf56bf179f07acdf872cd78e0d6ce129772028dd2d1a81821a002a804fa2581c36e8f59542d444e3afca00cecca881cbdf8257c1b07a2fd84ed25730a1574570737465696e4469646e744b696c6c48696d73656c661a000dbba0581c73ed4b685261a41c66ba62193572c3ce78652793df0a2d4a75c127cda14457454544195d5b021a0002b5c1031a01a206b7a400828258206c027d63a875e6ab52abc975594f6060dc8d8d9342a42899ff64e4b5a8041cc4018258207c1cc2472c9808612e77b75d2c253c9aa05d0a83cf1d72b929fb41cc2b8054ff00018282583901a9274ec0157902ffe776bca9a7b24aac3373044a3c71e0c27ec8f741a9274ec0157902ffe776bca9a7b24aac3373044a3c71e0c27ec8f7411a0e99550c82584c82d818584283581cfa4589909bc695cb0c7fbf23c37b5206fb180f4c5216a6d86c5caa40a101581e581c2b0b011ba3683d5a9b32802aa60b6f471a2bdab01598f0fa5d3ee332001ae706cac71b00000004d4f8ee05021a0002cb3d031a01a206c8a500818258207b9c5b2a8da20bdedb61f836c802ddf361fcc363faa7f64ecebcd3a5fcf8936105018182583901d3b46b29180c65567b03fcbdf0b897f4d3e58ee5ac9c7eb7b7a128e368b887d2e2b58bbb0c238406b77270089d0f370d9e28ab87c57d29f01a0049a28f021a0002a8b1031a01a20672048282008200581c68b887d2e2b58bbb0c238406b77270089d0f370d9e28ab87c57d29f083028200581c68b887d2e2b58bbb0c238406b77270089d0f370d9e28ab87c57d29f0581ce811a4b2f8ef3ec84143e3026d706564bc1cc98dc199a305e0fbb8e3a400818258200cda8d6a05ed90f3618b9a1d69cbaa112d7f508df405b3f7da7e4e348011219a01018282584c82d818584283581c9db8ac183b28b32e9fc70bb83345ff3f02cb6822086bccf9b272a56ea101581e581c6c62a9e3a4d9cc731469d72e5e097859162fe94cfb1149f980d46e36001a2c0ef42d1a12d1332082581d61d91ef01b73f3010bb173945cf5417257c00c002715a13052015ab54f1a166f4db4021a00029cd5031a01a1ee84a500818258204254c7128386731cdb4c709cb10c15080f0bb25a2e00ebaa7c4ba808811a5ece000181825839017c8c932fe58987c54752b4592ae73a9fccafa15553174ca7397eac61193e0d9a2f810bec4a2632006bba910de6dafb246ff3f6829fe3c8f81a755bc688021a00029e09031a01a206cb05a1581de1193e0d9a2f810bec4a2632006bba910de6dafb246ff3f6829fe3c8f81a0058a159a60081825820cd0bd822e080672736f6e10af4053eb7f5b36120fa154dfe647ada2a2a68e08e00018182581d71a5f1baef9bf194a48068c2545d5dd58e93da01a5c395a2f595c955ea821a08e513dba1581c5d2c310ba30ee79a9139defb690af87a110444492c9caf4b2038e0f1bf5443617264616e6f4b69647a303033384643323236015443617264616e6f4b69647a303033384643323237015443617264616e6f4b69647a303033384643323238015443617264616e6f4b69647a303033384643323239015443617264616e6f4b69647a303033384643323330015443617264616e6f4b69647a303033384643323331015443617264616e6f4b69647a303033384643323332015443617264616e6f4b69647a303033384643323333015443617264616e6f4b69647a303033384643323334015443617264616e6f4b69647a303033384643323335015443617264616e6f4b69647a303033384643323336015443617264616e6f4b69647a303033384643323337015443617264616e6f4b69647a303033384643323338015443617264616e6f4b69647a303033384643323339015443617264616e6f4b69647a303033384643323430015443617264616e6f4b69647a303033384643323431015443617264616e6f4b69647a303033384643323432015443617264616e6f4b69647a303033384643323433015443617264616e6f4b69647a303033384643323434015443617264616e6f4b69647a303033384643323435015443617264616e6f4b69647a303033384643323436015443617264616e6f4b69647a303033384643323437015443617264616e6f4b69647a303033384643323438015443617264616e6f4b69647a303033384643323439015443617264616e6f4b69647a303033384643323530015443617264616e6f4b69647a303033384643323531015443617264616e6f4b69647a303033384643323532015443617264616e6f4b69647a303033384643323533015443617264616e6f4b69647a303033384643323534015443617264616e6f4b69647a303033384643323535015443617264616e6f4b69647a303033384643323536015443617264616e6f4b69647a303033384643323537015443617264616e6f4b69647a303033384643323538015443617264616e6f4b69647a303033384643323539015443617264616e6f4b69647a303033384643323630015443617264616e6f4b69647a303033384643323631015443617264616e6f4b69647a303033384643323632015443617264616e6f4b69647a303033384643323633015443617264616e6f4b69647a303033384643323634015443617264616e6f4b69647a303033384643323635015443617264616e6f4b69647a303033384643323636015443617264616e6f4b69647a303033384643323637015443617264616e6f4b69647a303033384643323638015443617264616e6f4b69647a303033384643323639015443617264616e6f4b69647a303033384643323730015443617264616e6f4b69647a303033384643323731015443617264616e6f4b69647a303033384643323732015443617264616e6f4b69647a303033384643323733015443617264616e6f4b69647a303033384643323734015443617264616e6f4b69647a303033384643323735015443617264616e6f4b69647a303033384643323736015443617264616e6f4b69647a303033384643323737015443617264616e6f4b69647a303033384643323738015443617264616e6f4b69647a303033384643323739015443617264616e6f4b69647a303033384643323830015443617264616e6f4b69647a303033384643323831015443617264616e6f4b69647a303033384643323832015443617264616e6f4b69647a303033384643323833015443617264616e6f4b69647a303033384643323834015443617264616e6f4b69647a303033384643323835015443617264616e6f4b69647a303033384643323836015443617264616e6f4b69647a303033384643323837015443617264616e6f4b69647a303033384643323838015443617264616e6f4b69647a303033384643323839015443617264616e6f4b69647a303033384643323930015443617264616e6f4b69647a303033384643323931015443617264616e6f4b69647a303033384643323932015443617264616e6f4b69647a303033384643323933015443617264616e6f4b69647a303033384643323934015443617264616e6f4b69647a303033384643323935015443617264616e6f4b69647a303033384643323936015443617264616e6f4b69647a303033384643323937015443617264616e6f4b69647a303033384643323938015443617264616e6f4b69647a303033384643323939015443617264616e6f4b69647a303033384643333030015443617264616e6f4b69647a303033384643333031015443617264616e6f4b69647a30303338464333303201ff021a000bbda5031a01a4b0cb07582020203a2d332f99480ab1a242d20ab11fe4a3fe1deb7be47dd65d0605e260633709a1581c5d2c310ba30ee79a9139defb690af87a110444492c9caf4b2038e0f1bf5443617264616e6f4b69647a303033384643323236015443617264616e6f4b69647a303033384643323237015443617264616e6f4b69647a303033384643323238015443617264616e6f4b69647a303033384643323239015443617264616e6f4b69647a303033384643323330015443617264616e6f4b69647a303033384643323331015443617264616e6f4b69647a303033384643323332015443617264616e6f4b69647a303033384643323333015443617264616e6f4b69647a303033384643323334015443617264616e6f4b69647a303033384643323335015443617264616e6f4b69647a303033384643323336015443617264616e6f4b69647a303033384643323337015443617264616e6f4b69647a303033384643323338015443617264616e6f4b69647a303033384643323339015443617264616e6f4b69647a303033384643323430015443617264616e6f4b69647a303033384643323431015443617264616e6f4b69647a303033384643323432015443617264616e6f4b69647a303033384643323433015443617264616e6f4b69647a303033384643323434015443617264616e6f4b69647a303033384643323435015443617264616e6f4b69647a303033384643323436015443617264616e6f4b69647a303033384643323437015443617264616e6f4b69647a303033384643323438015443617264616e6f4b69647a303033384643323439015443617264616e6f4b69647a303033384643323530015443617264616e6f4b69647a303033384643323531015443617264616e6f4b69647a303033384643323532015443617264616e6f4b69647a303033384643323533015443617264616e6f4b69647a303033384643323534015443617264616e6f4b69647a303033384643323535015443617264616e6f4b69647a303033384643323536015443617264616e6f4b69647a303033384643323537015443617264616e6f4b69647a303033384643323538015443617264616e6f4b69647a303033384643323539015443617264616e6f4b69647a303033384643323630015443617264616e6f4b69647a303033384643323631015443617264616e6f4b69647a303033384643323632015443617264616e6f4b69647a303033384643323633015443617264616e6f4b69647a303033384643323634015443617264616e6f4b69647a303033384643323635015443617264616e6f4b69647a303033384643323636015443617264616e6f4b69647a303033384643323637015443617264616e6f4b69647a303033384643323638015443617264616e6f4b69647a303033384643323639015443617264616e6f4b69647a303033384643323730015443617264616e6f4b69647a303033384643323731015443617264616e6f4b69647a303033384643323732015443617264616e6f4b69647a303033384643323733015443617264616e6f4b69647a303033384643323734015443617264616e6f4b69647a303033384643323735015443617264616e6f4b69647a303033384643323736015443617264616e6f4b69647a303033384643323737015443617264616e6f4b69647a303033384643323738015443617264616e6f4b69647a303033384643323739015443617264616e6f4b69647a303033384643323830015443617264616e6f4b69647a303033384643323831015443617264616e6f4b69647a303033384643323832015443617264616e6f4b69647a303033384643323833015443617264616e6f4b69647a303033384643323834015443617264616e6f4b69647a303033384643323835015443617264616e6f4b69647a303033384643323836015443617264616e6f4b69647a303033384643323837015443617264616e6f4b69647a303033384643323838015443617264616e6f4b69647a303033384643323839015443617264616e6f4b69647a303033384643323930015443617264616e6f4b69647a303033384643323931015443617264616e6f4b69647a303033384643323932015443617264616e6f4b69647a303033384643323933015443617264616e6f4b69647a303033384643323934015443617264616e6f4b69647a303033384643323935015443617264616e6f4b69647a303033384643323936015443617264616e6f4b69647a303033384643323937015443617264616e6f4b69647a303033384643323938015443617264616e6f4b69647a303033384643323939015443617264616e6f4b69647a303033384643333030015443617264616e6f4b69647a303033384643333031015443617264616e6f4b69647a30303338464333303201ffa400818258206ee70506daa57d2de6544d9c0790beafe592a4daf124f8046ce22859fdd3c1a901018282584c82d818584283581cde35879ae16e897627eb20baf26be7e7bd325583bd17aa9c67713b0aa101581e581ce378ee30d5681449869d426d33e11d95dc7896854fefac8feea15d66001a7d335c381a08583b0082581d6179e67550b2ff311da1883ad0ccc6fb2bb7c75e5489acff735fcc68781a53a6fb32021a00029cd5031a01a1ee84a40083825820a396e38605b4b3fee941a69ca1736e17516be67d6b596ef7e854557cf918738900825820c9f12ee2577895339a74d286bf77134dbcca5f806dc955c3fa94567a0118cfdf0b8258207807540ef1577df8d3282fb18e157d1480535dd6f39648207bea759a28f3cdf217018282584c82d818584283581cdcfe05e2cb99a7e14c0142d453115ebc1ce1d00e68149eb762c47fc5a101581e581c7111304fa69dc3f1644da7a1db41603fa288f170bb5dc225f80da24b001aa9adf1101b00000002363e7f0082583901f765168cd378460d748f14d32cc462c62ef882f74bee0cc3fb38ecfd55ecd0372bb6dad0892a5154802adfe36be613d2c1690498059a8f631a813a2d36021a0002c45d031a01a206878ea10082825820e120d1e68b27660a9346947d2a3f79f5f702b66e58dfd542edc1443923ca89e058409c4e5759292092a773a2a14623f9a83799bd7297291f29e82cb552544765ba8c961dc12872067671e47fa141d2048a237407def2f6756cabfa682cca1c96960e825820b186a878bc2f6aae02a288abcbe1da7ebf6bd9125dc5a5e8e2addaf461c01a1e5840367d70340c36a58756fad5f2a25aef4317444b90cc96d5cbc7aab7c872c204ed7d3273f35fd75606a4b57124e2943e0ee87dedcd1f2c259e3d2a72684556e506a102828458201a66c53814b9b716280d25e928e28281984cecfa644a83cdabb5f2d9fdcdd5d258407ebe78a4009731b6ad5803147b086d7269c2727b4e21685de04f14b0b0a33b93159b167f180414d1696b779057ead145eb053f1bdd4a9a38948c5139aa4374055820deef140087f9cca73b50452ff9afe31fa179959cf7da3b341f7248a5d03d0fff5822a101581e581c9b1771bd305e4a4407f82ca9ce993f8be1c0f19a30307d6de3b5391c84582006334624d8da907649c765188d197b23e561c122c4b42835f26a91412c3e24865840f58e48db026fb30c3f2ac65cfb169ca31276eb73897c5ef495d1582cec422bf6e60efb69690199a9edaf424be7161bc24bd8a53a004662e990a11212f4d1e8025820dbd18cf22cb5c42e86e1dcc1a5d16fe24f2422e57be7a45f1b83e8c4ef18221f5822a101581e581c9b1771bd305e4a339d86c8a93fe0e379c4c7a24c04024e34b9a6659ca10082825820be12a95cfcd1011bfb59bd760ef683e040de4cb0355843683dfac00312b3535d58401ece9f0d7ea618e840bb8eeb2657baf19139d9b1c467e91345930ecf167a42e5ccd92ec95bf26038dc0a8b8cb404f5074990dffeda4d76bc61d9c7eac23965048258200d197b7eeef1b961962a2911d4b553f0d9c3114b8d3e61de3beb4f39dc348eb05840d3fc852714c1c741ab5effeaf50a47b449291d0613d4dd538aea58870a13819f05491726ccea72fc76407ee253f4aff23229a45604523fd5ced2c85dabfaf10ea10082825820d85a190eb4f193ae18c5395bf9d5493ceef6cffde1923fc3d41a3549f68f314b58400847fc53c36acf1f68f4de7b99afcea8706984958c4093d6db7d8e53c86e4daec7cb3ea313acc48b171ed6c3bf0c88951c4e62b1c80f43d72af54dbbeef0ff05825820c2aad03f77eb131a808e04efde662e7fb84034e811810c866cece5462b6dfc7b5840ec86663c28c1494d8ef004202c31747b038e36f7f0a446803bcf0c09f1b98f7ac3b539234b925292c6b88c96529501d3f614c92f83fb3d3eed0abef870cfe208a10083825820a080d8e4a44af1c986d93975807cf10f5d3670b7892f4c34cf91d7f12bbb4e6d5840156631a0c031bcc40e281b2f5405670cd8f87a2b04218a1d577337d1695d20319b6bd6490797b1be5abbc9d2c120469ce1f55fdffb6959bea43d1bb336bb1603825820a080d8e4a44af1c986d93975807cf10f5d3670b7892f4c34cf91d7f12bbb4e6d5840156631a0c031bcc40e281b2f5405670cd8f87a2b04218a1d577337d1695d20319b6bd6490797b1be5abbc9d2c120469ce1f55fdffb6959bea43d1bb336bb1603825820a080d8e4a44af1c986d93975807cf10f5d3670b7892f4c34cf91d7f12bbb4e6d5840156631a0c031bcc40e281b2f5405670cd8f87a2b04218a1d577337d1695d20319b6bd6490797b1be5abbc9d2c120469ce1f55fdffb6959bea43d1bb336bb1603a10081825820dbaa64dac9db1e42c8e7a65cb0a19b0d5bcd9d7492fbbdffa4aa1c6eeefef6055840134ddda8c219f15fcfc5a5760c51dd192d6e6c5765373b49c1f61b03e281ae65c702f53976f2e3ddf604e66dd5601edfa9b76cf7b7744bd56e99eb21ec3ac106a10081825820fa10a8aad3525bae5e6fc86f002a29fdeacf7e2c3adbbd42fb18ce37f46d6da758400b9c109eee0f20b17c2182c4b4356fe27ce61585a7b87c4454133ab9b4eeefa822290eb02210a6faa466113788e74f0b6f15dbd7564ad146b341b5d89aa34800a10282845820ca29c439ebe4e46749b5cd58ccd4a80c3e63590532b5e3e47929dcffadfe6f915840234f256bdaebabc73069dae65682f2bfd642854f37a901532d542537716f14445e4516cb7116d8fcf62b8dfe8d358ef76b3a8877b9e33f8e0ec49cc151b49d025820b5fa70b1f92b6f51f40a1214c83d072ee3463d37dd5a4ab93fd3880c1c2b78705822a101581e581c2b0b011ba3683d3b39e26d2a2a7538c41e52733b9a31fd976489dceb84582090f9faab0162b29b4ae704ec6b34b907ff4b460d95067416847c9de869fcffbd58402cc2a20369497238a6eafc0a73a9b20d0616a9b37769b1ca1f4b85fc0c787d0bb360255bd5008adc6a731b712886e13631f05ca293b1a0c7f0d6402e0e35800e582042273fb0f6b6eeae752bfa1efa18d3a52fe906cad33d9789f42875dccf122b3d5822a101581e581c2b0b011ba3683d6727811e2a7055eba994d24d6d5dbe938c685b65b1a100828258201bf61889f47fb0c57f000efc792c108058abd09ded1b92c8e321d5157dcc0b03584031ef0ade7e1c6aef0020f252650cc1e4278378d8292f68fef83c3cecb7d147fcb11dd12eddd16140ce3bc074f8ef96514558c7c031d7096da5eb9a8bb81c190282582068afabddeff38f0274543bdeca22982ba317c80534e2087b6e9ebb5bcbee2d255840dfc59eb00d76baed7333f8cf737fa9906b28bca9002a5484f0fb982d5458989536d6b5267d2d43cb7a032e166bb4df86a042c44f1c5a9c8fafd88dade88b8002a100818258200d197b7eeef1b961962a2911d4b553f0d9c3114b8d3e61de3beb4f39dc348eb05840d6863f876b33fb84a0f5ae757495136c1d38776a3ce3b7ce45ec4d19d51463fce731ce1d266e35c354ccee3e23b8f539c3122a6185594c2bca8530839f63620da10082825820c936c5031dfe7302a9577d20f39121a05186da762040ea7930ba40b4e75117095840dff4972a083973d9e4393520ab2263d47a8e9fd081e77215ba059eeaf188e3bbcd54f3efd20d6d711d9d29106b2d0ab484e37c24b72f82feb6adfe91eadc5901825820f9b39400c618265f9ba86e002b8df2eb114f2c5129a6c8cad4c749d024bcf73c584098147dd4e497e9aefcca6229e5ca1a28d43e2c6291d0ff627e9f4939341ebd80494fab7bad689f187c03b3d7fcbbf24fa7a04aabcd0c77455756f6ee4708560ca20081825820b58007aa837902c5f9deb45b0720ce9edb90231ab51d8d8a04223abbe91660c15840c592e5a6f8cc80c5897680120a23744aff93bdda1c39008090f5103d758dbe5284125ff5a2b8c060a4fceab1b2a86f5ececfc8ae016c7b352a0fe18d5778cc0e01828200581c026786de9ea6d7de570eb20d7bcb3c815bf88a46cdd9bcf7b5b357158201828200581c026786de9ea6d7de570eb20d7bcb3c815bf88a46cdd9bcf7b5b3571582051a01a4b0cba100818258206687a36f0a4b8abf2671f8affb7febeb8940e9ee83548d2a2924dbc74679c46658403523bc8f616d9e7e7035fbade81505c88d1ea80e1c6e47412b1024c79f20a1221ffbadc68fbc8bca68ba1818d5f5c0446691e409958e43e64993cba32b8d170aa100838258201e148f63439d8482ac734d737dd4bf1d0f2c7053ed9810beea3151f3f4545356584063945f682dd36acc2ba795588b6bd501a1809869d9f7f6106fd12e4bf2ed0b891e875915938c8cca59da16cfe8fe07f414a98923cc3a29e0495193c6a1aa6c008258201cb6da608e12fbed48e93574274838bd855d965661874e734eba54ab138517b15840da0fee21f01f79738d37dc995e9bb2b009e037e652a48742a16d8af9598ddaf800273d35d65a0ac110644dd6e06798592b43025f1a0c2802790c27374599b7028258207b42a33d3b1cc39f54db7273d589c66146f55d2fc3ec784ed596b5b737f8ee7d584094948408e251b18f1ffe93eff189b3bdfb033dacbd1fa8ca3dc8c76e19b6a44598db9968a77480feddd34dabdc8d41e18872e4f085840c5a8cba14dfa9765c03a10b82a11902d1a478383564326333313062613330656537396139313339646566623639306166383761313130343434343932633963616634623230333865306631b84d7443617264616e6f4b69647a303033384643323236a362696418e265696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3232365d7443617264616e6f4b69647a303033384643323237a362696418e365696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3232375d7443617264616e6f4b69647a303033384643323238a362696418e465696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3232385d7443617264616e6f4b69647a303033384643323239a362696418e565696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3232395d7443617264616e6f4b69647a303033384643323330a362696418e665696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233305d7443617264616e6f4b69647a303033384643323331a362696418e765696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233315d7443617264616e6f4b69647a303033384643323332a362696418e865696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233325d7443617264616e6f4b69647a303033384643323333a362696418e965696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233335d7443617264616e6f4b69647a303033384643323334a362696418ea65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233345d7443617264616e6f4b69647a303033384643323335a362696418eb65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233355d7443617264616e6f4b69647a303033384643323336a362696418ec65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233365d7443617264616e6f4b69647a303033384643323337a362696418ed65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233375d7443617264616e6f4b69647a303033384643323338a362696418ee65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233385d7443617264616e6f4b69647a303033384643323339a362696418ef65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3233395d7443617264616e6f4b69647a303033384643323430a362696418f065696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234305d7443617264616e6f4b69647a303033384643323431a362696418f165696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234315d7443617264616e6f4b69647a303033384643323432a362696418f265696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234325d7443617264616e6f4b69647a303033384643323433a362696418f365696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234335d7443617264616e6f4b69647a303033384643323434a362696418f465696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234345d7443617264616e6f4b69647a303033384643323435a362696418f565696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234355d7443617264616e6f4b69647a303033384643323436a362696418f665696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234365d7443617264616e6f4b69647a303033384643323437a362696418f765696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234375d7443617264616e6f4b69647a303033384643323438a362696418f865696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234385d7443617264616e6f4b69647a303033384643323439a362696418f965696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3234395d7443617264616e6f4b69647a303033384643323530a362696418fa65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235305d7443617264616e6f4b69647a303033384643323531a362696418fb65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235315d7443617264616e6f4b69647a303033384643323532a362696418fc65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235325d7443617264616e6f4b69647a303033384643323533a362696418fd65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235335d7443617264616e6f4b69647a303033384643323534a362696418fe65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235345d7443617264616e6f4b69647a303033384643323535a362696418ff65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235355d7443617264616e6f4b69647a303033384643323536a362696419010065696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235365d7443617264616e6f4b69647a303033384643323537a362696419010165696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235375d7443617264616e6f4b69647a303033384643323538a362696419010265696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235385d7443617264616e6f4b69647a303033384643323539a362696419010365696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3235395d7443617264616e6f4b69647a303033384643323630a362696419010465696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236305d7443617264616e6f4b69647a303033384643323631a362696419010565696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236315d7443617264616e6f4b69647a303033384643323632a362696419010665696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236325d7443617264616e6f4b69647a303033384643323633a362696419010765696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236335d7443617264616e6f4b69647a303033384643323634a362696419010865696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236345d7443617264616e6f4b69647a303033384643323635a362696419010965696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236355d7443617264616e6f4b69647a303033384643323636a362696419010a65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236365d7443617264616e6f4b69647a303033384643323637a362696419010b65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236375d7443617264616e6f4b69647a303033384643323638a362696419010c65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236385d7443617264616e6f4b69647a303033384643323639a362696419010d65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3236395d7443617264616e6f4b69647a303033384643323730a362696419010e65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237305d7443617264616e6f4b69647a303033384643323731a362696419010f65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237315d7443617264616e6f4b69647a303033384643323732a362696419011065696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237325d7443617264616e6f4b69647a303033384643323733a362696419011165696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237335d7443617264616e6f4b69647a303033384643323734a362696419011265696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237345d7443617264616e6f4b69647a303033384643323735a362696419011365696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237355d7443617264616e6f4b69647a303033384643323736a362696419011465696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237365d7443617264616e6f4b69647a303033384643323737a362696419011565696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237375d7443617264616e6f4b69647a303033384643323738a362696419011665696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237385d7443617264616e6f4b69647a303033384643323739a362696419011765696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3237395d7443617264616e6f4b69647a303033384643323830a362696419011865696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238305d7443617264616e6f4b69647a303033384643323831a362696419011965696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238315d7443617264616e6f4b69647a303033384643323832a362696419011a65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238325d7443617264616e6f4b69647a303033384643323833a362696419011b65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238335d7443617264616e6f4b69647a303033384643323834a362696419011c65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238345d7443617264616e6f4b69647a303033384643323835a362696419011d65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238355d7443617264616e6f4b69647a303033384643323836a362696419011e65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238365d7443617264616e6f4b69647a303033384643323837a362696419011f65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238375d7443617264616e6f4b69647a303033384643323838a362696419012065696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238385d7443617264616e6f4b69647a303033384643323839a362696419012165696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3238395d7443617264616e6f4b69647a303033384643323930a362696419012265696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239305d7443617264616e6f4b69647a303033384643323931a362696419012365696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239315d7443617264616e6f4b69647a303033384643323932a362696419012465696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239325d7443617264616e6f4b69647a303033384643323933a362696419012565696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239335d7443617264616e6f4b69647a303033384643323934a362696419012665696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239345d7443617264616e6f4b69647a303033384643323935a362696419012765696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239355d7443617264616e6f4b69647a303033384643323936a362696419012865696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239365d7443617264616e6f4b69647a303033384643323937a362696419012965696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239375d7443617264616e6f4b69647a303033384643323938a362696419012a65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239385d7443617264616e6f4b69647a303033384643323939a362696419012b65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3239395d7443617264616e6f4b69647a303033384643333030a362696419012c65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3330305d7443617264616e6f4b69647a303033384643333031a362696419012d65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3330315d7443617264616e6f4b69647a303033384643333032a362696419012e65696d616765783a697066733a2f2f697066732f516d6232704e3454536f4363333476687a6f59534363444b357936593833344c44794e6843715837556f55627752646e616d65781b43617264616e6f204b69647a204e46542030303338205b3330325d69636f70797269676874782f436f707972696768742043617264616e6f4b69647a20323032313b20616c6c20726967687473207265736572766564697075626c69736865728267636e66742e696f6f63617264616e6f6b69647a2e636f6d6776657273696f6e0180 \ No newline at end of file diff --git a/mithril-test-lab/test_data/blocks/shelley1.block b/mithril-test-lab/test_data/blocks/shelley1.block new file mode 100644 index 00000000000..66da341e9d4 --- /dev/null +++ b/mithril-test-lab/test_data/blocks/shelley1.block @@ -0,0 +1 @@ +820284828f1a004723dd1a007949425820c175f470d30216341423a98a6087175642250acec7d9f53a311cf2e0a1c9c7b258208b53207629f9a30e4b2015044f337c01735abe67243c19470c9dae8c7b732798582090561cf5fb4eada778f0564060b9b5138fbfa50c0e74fc496956c8c3507301a6825840d266d923d59fc8a1b7e964dab2b6db804b494c202586eae8e2db929ca2361d9f01154c4a78b95a2e6bf19ebe98e775f894ad53971bd1ceeee125ee8473747d60585011614e11e284d28aa303da9ca3a37bfde35f931d308ae3da36e381ac42910d36dc26d91bfa726d7b4a7ae1fb263e037e8f9e80e3411a8754863b8b5601047b9e04d0f72f00206ea616c6cffc75fc48018258405620f9239d562aed34442b72c8bc840bb9a5ef897b470132430a02cd0ce69052a6ebb17896177180c1d88afed3d7614878549c5573c0f281d5dad2f29bda5a6d5850f349597045cc5f65a9724770f971e6964e09fd85db8e36ef789f390afd4629a3f5e96b4e5ee8280ec26236a6323cbc16867a1868645566e0607d7a474fd7d06b44c3afbcd85a41098a80ba6faeb7400b190596582000ef8e1bebe7d404a910c7c467fb5aafbc7dee7fcaac94cb9693e08ea9dd7d2a5820674617ebe299bcba144026e4342e9f54c861165c1dde1373fd1206e654f985b800183758405befdeffa73bc8b4a1cd22aa2c896f189a698175e5bfc4a562e3a15b5f6580953e6fcc72a37386816031e36fdf19718351417f01af02c7314fbe9f2792b29e0c02005901c0c597caba74923b7901d5b8162f27338413b12941e411e9371b06a01375b8690aab067a42dde22db909bf77db373ca8645b751711256ba5f360e2935f64d14104cfdfb6c7865c6f4219e67af060cdcf4dc3d874ede00c394e9ecac7ba1f663b367e0f482e1bfaff08808d6567590cd6bf43c849ebacfb5fb185f4592ff3bf0a479d5a1f3f19f819b59f662cad2b6ff2187ec94b4c5fac6b8375b02d6b52d229ae24b389ff2d72b584f47f77cbc62a43f1880e486fda30ac1600f475dc4857e66090fe7399f4e3bff4929ea1c1929371846a34391473c79f9409f05f65fe8d2acb6f5eceb84474555d163db96d809aa77b9c2f80156d0356e75204ab5032e833bbacecf407038c8a28da4900c1c63a5bb32672dd345c37e1c866b15da5d2c41ab76c214bc8e3efd9e34cf092f1166edc2de2b03ccaca01b2c0261bfaf3f166f3937c21128c3ebb96ceefab6c80897da9f096a7cc113c4b9c0cd8b97fe3d29f6a2c9960005d2f1ce2e8bfeca9b1f8ebe80637f59133692e11ad9f9c557c10102472aef7f472d72920bbff7a7f4e344988c2d5f98482fbbae7a081d7f10b55b33a7c4dea90483223bea2093cb068b2db39973dd06700ea4eb65fa3210d7d53430c9c84a5008182582050eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb200018182583901f53fd6f6b96f74cc90fd995afad1bfdbd49ff7d04fc9e7a2f81285b75c465cbf8c5536970e8a29bb7adcda0d663b20007d481813694c64ef1b0000021f05a734bb021a0002a389031a0079652c048183028200581c5c465cbf8c5536970e8a29bb7adcda0d663b20007d481813694c64ef581c6b5180a258275c671690c94c704f074190e90ea900ed565b4c29abe8a50081825820368f24c09763ee846f68c60f987eeca4d9a7e9cb537cea2cc593e77dd9c0ac67000181825839019a5d1ab9ca1eb592973a0d3f5ffdd006e7e4c0be6a7b43211325168932dd8b472ba9876597a01259896cdb4650a62402aa9737175d539fe81b00000045d92911df021a0002a389031a0079652c048183028200581c32dd8b472ba9876597a01259896cdb4650a62402aa9737175d539fe8581c76e80e1b3f622c7051f222453497b0667e12892f5d94ee565d17dc52a40081825820cffac1e93f583d1f272142df5b793666e613c66cea4e500b18437aded85febbc00018282581d614497e18047e5c891ead6a215b3f5dce026921a8b76c7fe27a2ba9f681a000f424082583901a76b7122a3773792b6275c9a0fb0bf4ae0c17c52363de66062eca50c13ce7fae17ab762c4390c4d77bd74f7d4651af480508ea237b1dd0b51a0086a7f3021a0002ac4d031a007950a4a400828258201c3b9c2016d9ae6b3e86e1888296e6bc180e6bcfd344711b7305651ae90f87e60182582038d2f2cf6a2ce8563cc3b84b4b0e003404363a352fe1ac7ef5a6b5bb014c3d6c07018282582b82d818582183581c5f6712df165e03b5eb5e72e50058a181777696b222c54d844944da14a0001add85ea5a1a22dad5bd82581d614db3b10bedda3b9dd3d3a8886ca9d8a581162b874a7460edaab406fb1a032fcbef021a0003447f031a0098968084a10082825820e3968f45134a774f66b011c2c463ad64a5ba7e17f4774be460d08477a07e31f458402c551fc904b8e55a58d437e744566d0307a186af8f08d51a637456e0eb8247a0b639441a4fb25807b46edd60766a9e766b19e7c33caf5eea1752fc017b840100825820590a7c4229d9aede28c43f2626fcd36bc383cc3e622a654ad962e4c49570938c584042f3ed22755c5523a66864cb644330b82ace0b7cd483f4ea4e4c4d3ff88e62785811bf6a7da08e386f218301ff50bf8d000ca3df07dd831b9160b469ac95110ca10082825820feef653d2ba57531b9a442258cc0ad12feda29141b3e2a05e4a037262c4bb34d5840def966f7d8ea6e9ceb519606a9b7076b85677b6edcebd386f42f71c5c942c6ef90cbfad2265634cf5ebae00f8b86760151c6559bc82b52c130547eed5414120b825820d91dc0521946003bc7ded1a54938a04bee80a860382c51ef7c9e4563346979345840ab999c3cbb8edd48da9b884a4dcb86a8e7b7110ee08f312264a018fa6d50e4a0635f124916be7773c38c8bec8a0fa7f2ac5058a776932ebfd557a8a263552409a100818258205ee8a66efac254382955ec4d643a9f24015d0ac1ed32480b1b7235f10e87a0e458400fc033349c1a9ecf966902e05f35b7459a642f7b3818c80003b08179631521c810a8a52e30c5211a38a88732369fdf07d96c83963564f11b14048c060a2d410ea10082825820a1ecd258e4f23d09803873e6b8a5ed21107738e12004ed7b1fe1bf89c9c49e5a5840789070b47a7d4bf6ee4d56773ace2faaed2f76905848d65e5c35bfb75339060ec9c6e904b9a8fc54cf861a6da4aa3d8f607cb325b1f1cc266e49fb8a6dc3ef01825820a1ecd258e4f23d09803873e6b8a5ed21107738e12004ed7b1fe1bf89c9c49e5a5840789070b47a7d4bf6ee4d56773ace2faaed2f76905848d65e5c35bfb75339060ec9c6e904b9a8fc54cf861a6da4aa3d8f607cb325b1f1cc266e49fb8a6dc3ef01a0 \ No newline at end of file diff --git a/mithril-test-lab/test_data/immutable/00000.chunk b/mithril-test-lab/test_data/immutable/00000.chunk new file mode 100644 index 0000000000000000000000000000000000000000..8b9722ca0939de19a04d2754ae6e443e1b7dcab2 GIT binary patch literal 25550 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 literal 0 HcmV?d00001 diff --git a/mithril-test-lab/test_data/immutable/00000.primary b/mithril-test-lab/test_data/immutable/00000.primary new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00000.secondary b/mithril-test-lab/test_data/immutable/00000.secondary new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00001.chunk b/mithril-test-lab/test_data/immutable/00001.chunk new file mode 100644 index 0000000000000000000000000000000000000000..d91e1c1359e74e2bc29977b0d9d3fb998ee21f4a GIT binary patch literal 5925 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 I;bOu61Ekl0Z~y=R literal 0 HcmV?d00001 diff --git a/mithril-test-lab/test_data/immutable/00001.primary b/mithril-test-lab/test_data/immutable/00001.primary new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00001.secondary b/mithril-test-lab/test_data/immutable/00001.secondary new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00002.chunk b/mithril-test-lab/test_data/immutable/00002.chunk new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00002.primary b/mithril-test-lab/test_data/immutable/00002.primary new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-test-lab/test_data/immutable/00002.secondary b/mithril-test-lab/test_data/immutable/00002.secondary new file mode 100644 index 00000000000..e69de29bb2d From 9fbdadee7620b47c12de375664ebb1cca92c3e49 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 22 Jan 2024 17:11:08 +0100 Subject: [PATCH 11/25] feat: implement Cardano transactions store in aggregator --- .../database/provider/cardano_transactions.rs | 226 +++++++++++++++++- .../src/dependency_injection/builder.rs | 46 +++- .../src/dependency_injection/containers.rs | 5 +- .../signable_builder/cardano_transactions.rs | 14 ++ mithril-common/src/store/mod.rs | 4 + mithril-common/src/store/transaction_store.rs | 13 + mithril-signer/src/runtime/runner.rs | 17 +- mithril-signer/src/runtime/signer_services.rs | 1 + .../test_extensions/state_machine_tester.rs | 4 +- 9 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 mithril-common/src/store/transaction_store.rs diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transactions.rs index 55be900dc46..d24ea288705 100644 --- a/mithril-aggregator/src/database/provider/cardano_transactions.rs +++ b/mithril-aggregator/src/database/provider/cardano_transactions.rs @@ -1,14 +1,17 @@ use mithril_common::{ - entities::{BlockNumber, ImmutableFileNumber, TransactionHash}, + entities::{BlockNumber, CardanoTransaction, ImmutableFileNumber, TransactionHash}, sqlite::{ HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition, }, + store::TransactionStore, StdResult, }; +use anyhow::Context; +use async_trait::async_trait; use sqlite::{Row, Value}; -use std::sync::Arc; +use std::{iter::repeat, sync::Arc}; /// Cardano Transaction record is the representation of a cardano transaction. #[derive(Debug, PartialEq, Clone)] @@ -23,6 +26,16 @@ pub struct CardanoTransactionRecord { pub immutable_file_number: ImmutableFileNumber, } +impl From for CardanoTransactionRecord { + fn from(transaction: CardanoTransaction) -> Self { + Self { + transaction_hash: transaction.transaction_hash, + block_number: transaction.block_number, + immutable_file_number: transaction.immutable_file_number, + } + } +} + impl SqLiteEntity for CardanoTransactionRecord { fn hydrate(row: Row) -> Result where @@ -116,6 +129,32 @@ impl<'client> InsertCardanoTransactionProvider<'client> { Ok(WhereCondition::new(expression, parameters)) } + + fn get_insert_many_condition( + &self, + transactions_records: Vec, + ) -> WhereCondition { + let columns = "(transaction_hash, block_number, immutable_file_number)"; + let values_columns: Vec<&str> = repeat("(?*, ?*, ?*)") + .take(transactions_records.len()) + .collect(); + + let values: Vec = transactions_records + .into_iter() + .flat_map(|record| { + vec![ + Value::String(record.transaction_hash), + Value::Integer(record.block_number.try_into().unwrap()), + Value::Integer(record.immutable_file_number.try_into().unwrap()), + ] + }) + .collect(); + + WhereCondition::new( + format!("{columns} values {}", values_columns.join(", ")).as_str(), + values, + ) + } } impl<'client> Provider<'client> for InsertCardanoTransactionProvider<'client> { @@ -129,7 +168,7 @@ impl<'client> Provider<'client> for InsertCardanoTransactionProvider<'client> { let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); let projection = Self::Entity::get_projection().expand(aliases); - format!("insert into cardano_tx {condition} returning {projection}") + format!("insert or ignore into cardano_tx {condition} returning {projection}") } } @@ -165,7 +204,7 @@ impl CardanoTransactionRepository { transaction_hash: &TransactionHash, block_number: BlockNumber, immutable_file_number: ImmutableFileNumber, - ) -> StdResult { + ) -> StdResult> { let provider = InsertCardanoTransactionProvider::new(&self.connection); let filters = provider.get_insert_condition(&CardanoTransactionRecord { transaction_hash: transaction_hash.to_owned(), @@ -174,9 +213,32 @@ impl CardanoTransactionRepository { })?; let mut cursor = provider.find(filters)?; - cursor - .next() - .ok_or_else(|| panic!("Inserting a Cardano transaction should not return nothing.")) + Ok(cursor.next()) + } + + /// Create new [CardanoTransactionRecord]s in the database. + pub async fn create_transactions( + &self, + transactions: Vec, + ) -> StdResult> { + let provider = InsertCardanoTransactionProvider::new(&self.connection); + let filters = provider.get_insert_many_condition(transactions); + let cursor = provider.find(filters)?; + + Ok(cursor.collect()) + } +} + +#[async_trait] +impl TransactionStore for CardanoTransactionRepository { + async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()> { + let records: Vec = + transactions.iter().map(|tx| tx.to_owned().into()).collect(); + self.create_transactions(records) + .await + .with_context(|| "CardanoTransactionRepository can not store transactions")?; + + Ok(()) } } @@ -259,6 +321,43 @@ mod tests { ); } + #[test] + fn insert_provider_many_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = InsertCardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_insert_many_condition(vec![ + CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }, + CardanoTransactionRecord { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }, + ]) + .expand(); + + assert_eq!( + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3), (?4, ?5, ?6)" + .to_string(), + expr + ); + assert_eq!( + vec![ + Value::String("tx-hash-123".to_string()), + Value::Integer(10), + Value::Integer(99), + Value::String("tx-hash-456".to_string()), + Value::Integer(11), + Value::Integer(100) + ], + params + ); + } + #[tokio::test] async fn repository_create_and_get_transaction() { let connection = get_connection().await; @@ -285,4 +384,117 @@ mod tests { transaction_result ); } + + #[tokio::test] + async fn repository_create_ignore_further_transactions_when_exists() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + repository + .create_transaction(&"tx-hash-123".to_string(), 10, 99) + .await + .unwrap(); + repository + .create_transaction(&"tx-hash-123".to_string(), 11, 100) + .await + .unwrap(); + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + } + + #[tokio::test] + async fn repository_store_transactions_and_get_stored_transactions() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + + let cardano_transactions = vec![ + CardanoTransaction { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }, + CardanoTransaction { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }, + ]; + repository + .store_transactions(&cardano_transactions) + .await + .unwrap(); + + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + + let transaction_result = repository + .get_transaction(&"tx-hash-456".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }), + transaction_result + ); + } + + #[tokio::test] + async fn repository_store_transactions_doesnt_erase_existing_data() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + + repository + .create_transaction(&"tx-hash-000".to_string(), 1, 9) + .await + .unwrap(); + + let cardano_transactions = vec![CardanoTransaction { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }]; + repository + .store_transactions(&cardano_transactions) + .await + .unwrap(); + + let transaction_result = repository + .get_transaction(&"tx-hash-000".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-000".to_string(), + block_number: 1, + immutable_file_number: 9 + }), + transaction_result + ); + } } diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index e23233a966e..5840331916a 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -39,8 +39,11 @@ use mithril_common::{ CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, SignableBuilderService, }, sqlite::SqliteConnection, - store::adapter::{MemoryAdapter, SQLiteAdapter, StoreAdapter}, - BeaconProvider, BeaconProviderImpl, + store::{ + adapter::{MemoryAdapter, SQLiteAdapter, StoreAdapter}, + TransactionStore, + }, + BeaconProvider, BeaconProviderImpl, CardanoTransactionParser, }; use crate::{ @@ -50,9 +53,9 @@ use crate::{ }, configuration::ExecutionEnvironment, database::provider::{ - CertificateRepository, EpochSettingStore, OpenMessageRepository, SignedEntityStoreAdapter, - SignedEntityStorer, SignerRegistrationStore, SignerStore, SingleSignatureRepository, - StakePoolStore, + CardanoTransactionRepository, CertificateRepository, EpochSettingStore, + OpenMessageRepository, SignedEntityStoreAdapter, SignedEntityStorer, + SignerRegistrationStore, SignerStore, SingleSignatureRepository, StakePoolStore, }, event_store::{EventMessage, EventStore, TransmitterService}, http_server::routes::router, @@ -129,6 +132,9 @@ pub struct DependenciesBuilder { /// Beacon provider service. pub beacon_provider: Option>, + /// Cardano transactions store. + pub transaction_store: Option>, + /// Cardano transactions parser. pub transaction_parser: Option>, @@ -224,6 +230,7 @@ impl DependenciesBuilder { chain_observer: None, beacon_provider: None, transaction_parser: None, + transaction_store: None, immutable_digester: None, immutable_file_observer: None, immutable_cache_provider: None, @@ -664,16 +671,27 @@ impl DependenciesBuilder { self.create_logger().await } + async fn build_transaction_store(&mut self) -> Result> { + let transaction_store = CardanoTransactionRepository::new( + self.get_sqlite_connection_cardano_transactions().await?, + ); + + Ok(Arc::new(transaction_store)) + } + + /// Transaction store. + pub async fn get_transaction_store(&mut self) -> Result> { + if self.transaction_store.is_none() { + self.transaction_store = Some(self.build_transaction_store().await?); + } + + Ok(self.transaction_store.as_ref().cloned().unwrap()) + } + async fn build_transaction_parser(&mut self) -> Result> { - todo!() - /* let immutable_digester_cache = match self.configuration.environment { - ExecutionEnvironment::Production => Some(self.get_immutable_cache_provider().await?), - _ => None, - }; - let digester = - Car::new(immutable_digester_cache, self.get_logger().await?); + let transaction_parser = CardanoTransactionParser::default(); - Ok(Arc::new(digester)) */ + Ok(Arc::new(transaction_parser)) } /// Transaction parser. @@ -1008,6 +1026,7 @@ impl DependenciesBuilder { )); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( self.get_transaction_parser().await?, + self.get_transaction_store().await?, &self.configuration.db_directory, self.get_logger().await?, )); @@ -1147,6 +1166,7 @@ impl DependenciesBuilder { signer_getter: self.get_signer_store().await?, message_service: self.get_message_service().await?, cardano_transactions_parser: self.get_transaction_parser().await?, + cardano_transactions_store: self.get_transaction_store().await?, }; Ok(dependency_manager) diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index 97e57c38b05..eb3ae4a0d52 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -12,7 +12,7 @@ use mithril_common::{ era::{EraChecker, EraReader}, signable_builder::SignableBuilderService, sqlite::SqliteConnection, - store::StakeStorer, + store::{StakeStorer, TransactionStore}, test_utils::MithrilFixture, BeaconProvider, }; @@ -83,6 +83,9 @@ pub struct DependencyContainer { /// Beacon provider service. pub beacon_provider: Arc, + /// Cardano transactions store. + pub cardano_transactions_store: Arc, + /// Cardano transactions parser. pub cardano_transactions_parser: Arc, diff --git a/mithril-common/src/signable_builder/cardano_transactions.rs b/mithril-common/src/signable_builder/cardano_transactions.rs index 21df1edd92f..782b4a1ad2c 100644 --- a/mithril-common/src/signable_builder/cardano_transactions.rs +++ b/mithril-common/src/signable_builder/cardano_transactions.rs @@ -10,12 +10,14 @@ use crate::{ cardano_transactions_parser::TransactionParser, entities::{Beacon, ProtocolMessage, ProtocolMessagePartKey}, signable_builder::SignableBuilder, + store::TransactionStore, StdResult, }; /// A [CardanoTransactionsSignableBuilder] builder pub struct CardanoTransactionsSignableBuilder { transaction_parser: Arc, + transaction_store: Arc, logger: Logger, dirpath: PathBuf, } @@ -24,11 +26,13 @@ impl CardanoTransactionsSignableBuilder { /// Constructor pub fn new( transaction_parser: Arc, + transaction_store: Arc, dirpath: &Path, logger: Logger, ) -> Self { Self { transaction_parser, + transaction_store, logger, dirpath: dirpath.to_owned(), } @@ -54,6 +58,13 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { transactions.len() ); + let transaction_chunk_size = 100; + for transactions_in_chunk in transactions.chunks(transaction_chunk_size) { + self.transaction_store + .store_transactions(transactions_in_chunk) + .await?; + } + let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part( ProtocolMessagePartKey::CardanoTransactionsMerkleRoot, @@ -67,6 +78,7 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { #[cfg(test)] mod tests { use crate::cardano_transactions_parser::DumbTransactionParser; + use crate::store::MockTransactionStore; use super::*; use slog::Drain; @@ -83,8 +95,10 @@ mod tests { let beacon = Beacon::default(); let transactions_count = 0; let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let transaction_store = Arc::new(MockTransactionStore::new()); let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::new( transaction_parser, + transaction_store, Path::new("/tmp"), create_logger(), ); diff --git a/mithril-common/src/store/mod.rs b/mithril-common/src/store/mod.rs index ba017e2e98f..5ec5574fd52 100644 --- a/mithril-common/src/store/mod.rs +++ b/mithril-common/src/store/mod.rs @@ -4,6 +4,10 @@ pub mod adapter; mod stake_store; mod store_pruner; +mod transaction_store; pub use stake_store::{StakeStore, StakeStorer}; pub use store_pruner::StorePruner; +#[cfg(test)] +pub use transaction_store::MockTransactionStore; +pub use transaction_store::TransactionStore; diff --git a/mithril-common/src/store/transaction_store.rs b/mithril-common/src/store/transaction_store.rs new file mode 100644 index 00000000000..a251b800af4 --- /dev/null +++ b/mithril-common/src/store/transaction_store.rs @@ -0,0 +1,13 @@ +use crate::{entities::CardanoTransaction, StdResult}; +use async_trait::async_trait; + +#[cfg(test)] +use mockall::automock; + +/// Cardano transactions store +#[cfg_attr(test, automock)] +#[async_trait] +pub trait TransactionStore: Send + Sync { + /// Store list of transactions + async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()>; +} diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index 1c6ab7f63b2..f13e3ffa23b 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -454,7 +454,7 @@ mod tests { chain_observer::{ChainObserver, FakeObserver}, crypto_helper::ProtocolInitializer, digesters::{DumbImmutableDigester, DumbImmutableFileObserver}, - entities::{Epoch, StakeDistribution}, + entities::{CardanoTransaction, Epoch, StakeDistribution}, era::{ adapters::{EraReaderAdapterType, EraReaderBootstrapAdapter}, EraChecker, EraReader, @@ -465,7 +465,7 @@ mod tests { }, store::{ adapter::{DumbStoreAdapter, MemoryAdapter}, - StakeStore, StakeStorer, + StakeStore, StakeStorer, TransactionStore, }, test_utils::{fake_data, MithrilFixtureBuilder}, BeaconProvider, BeaconProviderImpl, CardanoNetwork, DumbTransactionParser, @@ -483,6 +483,17 @@ mod tests { use super::*; + mock! { + TransactionStoreImpl { } + + #[async_trait] + impl TransactionStore for TransactionStoreImpl + { + async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()>; + + } + } + const DIGESTER_RESULT: &str = "a digest"; mock! { @@ -527,8 +538,10 @@ mod tests { let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let transaction_store = Arc::new(MockTransactionStoreImpl::new()); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser.clone(), + transaction_store.clone(), Path::new(""), slog_scope::logger(), )); diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs index 951d08ebebe..f309efd034c 100644 --- a/mithril-signer/src/runtime/signer_services.rs +++ b/mithril-signer/src/runtime/signer_services.rs @@ -253,6 +253,7 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser, + todo!(), &self.config.db_directory, slog_scope::logger(), )); diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 1134f29028b..7dd7fda2da7 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -18,7 +18,7 @@ use mithril_common::{ CardanoImmutableFilesFullSignableBuilder, CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, MithrilStakeDistributionSignableBuilder, }, - store::{adapter::MemoryAdapter, StakeStore, StakeStorer}, + store::{adapter::MemoryAdapter, StakeStore, StakeStorer, TransactionStore}, BeaconProvider, BeaconProviderImpl, DumbTransactionParser, StdError, }; @@ -158,8 +158,10 @@ impl StateMachineTester { let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let transaction_store: Arc = todo!(); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser.clone(), + transaction_store.clone(), Path::new(""), slog_scope::logger(), )); From 34e495ae0d20cad713460d7fe15077f1b4613ffb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 22 Jan 2024 17:57:15 +0100 Subject: [PATCH 12/25] feat: implement Cardano transaction store in signer --- mithril-signer/src/configuration.rs | 34 +- .../cardano_transactions_migration.rs | 26 + mithril-signer/src/database/mod.rs | 2 + .../database/provider/cardano_transactions.rs | 504 ++++++++++++++++++ mithril-signer/src/database/provider/mod.rs | 5 + mithril-signer/src/lib.rs | 5 + mithril-signer/src/runtime/signer_services.rs | 39 +- mithril-signer/src/transaction_store.rs | 0 .../test_extensions/state_machine_tester.rs | 52 +- 9 files changed, 618 insertions(+), 49 deletions(-) create mode 100644 mithril-signer/src/database/cardano_transactions_migration.rs create mode 100644 mithril-signer/src/database/provider/cardano_transactions.rs create mode 100644 mithril-signer/src/database/provider/mod.rs create mode 100644 mithril-signer/src/transaction_store.rs diff --git a/mithril-signer/src/configuration.rs b/mithril-signer/src/configuration.rs index a96373c7b8f..68c78b65aa7 100644 --- a/mithril-signer/src/configuration.rs +++ b/mithril-signer/src/configuration.rs @@ -5,6 +5,7 @@ use std::{path::PathBuf, sync::Arc}; use mithril_common::{ chain_observer::ChainObserver, + crypto_helper::tests_setup, entities::PartyId, era::{ adapters::{EraReaderAdapterBuilder, EraReaderAdapterType}, @@ -13,8 +14,6 @@ use mithril_common::{ CardanoNetwork, StdResult, }; -const SQLITE_FILE: &str = "signer.sqlite3"; - /// Client configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Configuration { @@ -76,6 +75,33 @@ pub struct Configuration { } impl Configuration { + /// Create a sample configuration mainly for tests + #[doc(hidden)] + pub fn new_sample(party_id: &PartyId) -> Self { + let signer_temp_dir = tests_setup::setup_temp_directory_for_signer(party_id, false); + Self { + aggregator_endpoint: "http://0.0.0.0:8000".to_string(), + relay_endpoint: None, + cardano_cli_path: PathBuf::new(), + cardano_node_socket_path: PathBuf::new(), + db_directory: PathBuf::new(), + network: "devnet".to_string(), + network_magic: Some(42), + party_id: Some(party_id.to_owned()), + run_interval: 5000, + data_stores_directory: PathBuf::new(), + store_retention_limit: None, + kes_secret_key_path: signer_temp_dir.as_ref().map(|dir| dir.join("kes.sk")), + operational_certificate_path: signer_temp_dir + .as_ref() + .map(|dir| dir.join("opcert.cert")), + disable_digests_cache: false, + reset_digests_cache: false, + era_reader_adapter_type: EraReaderAdapterType::Bootstrap, + era_reader_adapter_params: None, + } + } + /// Return the CardanoNetwork value from the configuration. pub fn get_network(&self) -> StdResult { CardanoNetwork::from_code(self.network.clone(), self.network_magic).with_context(|| { @@ -88,7 +114,7 @@ impl Configuration { /// Create the SQL store directory if not exist and return the path of the /// SQLite3 file. - pub fn get_sqlite_file(&self) -> StdResult { + pub fn get_sqlite_file(&self, sqlite_file_name: &str) -> StdResult { let store_dir = &self.data_stores_directory; if !store_dir.exists() { @@ -100,7 +126,7 @@ impl Configuration { })?; } - Ok(self.data_stores_directory.join(SQLITE_FILE)) + Ok(self.data_stores_directory.join(sqlite_file_name)) } /// Create era reader adapter from configuration settings. diff --git a/mithril-signer/src/database/cardano_transactions_migration.rs b/mithril-signer/src/database/cardano_transactions_migration.rs new file mode 100644 index 00000000000..116d8ea9933 --- /dev/null +++ b/mithril-signer/src/database/cardano_transactions_migration.rs @@ -0,0 +1,26 @@ +//! Migration module for cardano transactions store +//! +use mithril_common::database::SqlMigration; + +/// Get all the migrations required by this version of the software. +/// There shall be one migration per database version. There could be several +/// statements per migration. +pub fn get_migrations() -> Vec { + vec![ + // Migration 1 + // Add the `cardano_tx` table. + SqlMigration::new( + 1, + r#" +create table cardano_tx ( + transaction_hash text not null, + block_number integer not null, + immutable_file_number integer not null, + primary key (transaction_hash) +); + +create unique index cardano_transactions_unique_index on cardano_tx(immutable_file_number); +"#, + ), + ] +} diff --git a/mithril-signer/src/database/mod.rs b/mithril-signer/src/database/mod.rs index 30e58786aa7..debe3352c7d 100644 --- a/mithril-signer/src/database/mod.rs +++ b/mithril-signer/src/database/mod.rs @@ -1,4 +1,6 @@ //! database module. //! This module contains the entities definition tied with database //! representation with their associated providers. +pub mod cardano_transactions_migration; pub mod migration; +pub mod provider; diff --git a/mithril-signer/src/database/provider/cardano_transactions.rs b/mithril-signer/src/database/provider/cardano_transactions.rs new file mode 100644 index 00000000000..a765b81a339 --- /dev/null +++ b/mithril-signer/src/database/provider/cardano_transactions.rs @@ -0,0 +1,504 @@ +use mithril_common::{ + entities::{BlockNumber, CardanoTransaction, ImmutableFileNumber, TransactionHash}, + sqlite::{ + HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, + WhereCondition, + }, + store::TransactionStore, + StdResult, +}; + +use anyhow::Context; +use async_trait::async_trait; +use sqlite::{Row, Value}; +use std::{iter::repeat, sync::Arc}; + +/// Cardano Transaction record is the representation of a cardano transaction. +#[derive(Debug, PartialEq, Clone)] +pub struct CardanoTransactionRecord { + /// Unique hash of the transaction + pub transaction_hash: TransactionHash, + + /// Block number of the transaction + pub block_number: BlockNumber, + + /// Immutable file number of the transaction + pub immutable_file_number: ImmutableFileNumber, +} + +impl From for CardanoTransactionRecord { + fn from(transaction: CardanoTransaction) -> Self { + Self { + transaction_hash: transaction.transaction_hash, + block_number: transaction.block_number, + immutable_file_number: transaction.immutable_file_number, + } + } +} + +impl SqLiteEntity for CardanoTransactionRecord { + fn hydrate(row: Row) -> Result + where + Self: Sized, + { + let transaction_hash = row.read::<&str, _>(0); + let block_number = row.read::(1); + let block_number = u64::try_from(block_number) + .map_err(|e| HydrationError::InvalidData(format!("Integer field cardano_tx.block_number (value={block_number}) is incompatible with u64 representation. Error = {e}")))?; + let immutable_file_number = row.read::(2); + let immutable_file_number = u64::try_from(immutable_file_number) + .map_err(|e| HydrationError::InvalidData(format!("Integer field cardano_tx.immutable_file_number (value={immutable_file_number}) is incompatible with u64 representation. Error = {e}")))?; + + Ok(Self { + transaction_hash: transaction_hash.to_string(), + block_number, + immutable_file_number, + }) + } + + fn get_projection() -> Projection { + Projection::from(&[ + ( + "transaction_hash", + "{:cardano_tx:}.transaction_hash", + "text", + ), + ("block_number", "{:cardano_tx:}.block_number", "int"), + ( + "immutable_file_number", + "{:cardano_tx:}.immutable_file_number", + "int", + ), + ]) + } +} + +struct CardanoTransactionProvider<'client> { + connection: &'client SqliteConnection, +} + +impl<'client> CardanoTransactionProvider<'client> { + pub fn new(connection: &'client SqliteConnection) -> Self { + Self { connection } + } + + // Useful in test and probably in the future. + #[allow(dead_code)] + fn get_transaction_hash_condition(&self, transaction_hash: &TransactionHash) -> WhereCondition { + WhereCondition::new( + "transaction_hash = ?*", + vec![Value::String(transaction_hash.to_owned())], + ) + } +} + +impl<'client> Provider<'client> for CardanoTransactionProvider<'client> { + type Entity = CardanoTransactionRecord; + + fn get_connection(&'client self) -> &'client SqliteConnection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!( + "select {projection} from cardano_tx where {condition} order by transaction_hash desc" + ) + } +} + +struct InsertCardanoTransactionProvider<'client> { + connection: &'client SqliteConnection, +} + +impl<'client> InsertCardanoTransactionProvider<'client> { + pub fn new(connection: &'client SqliteConnection) -> Self { + Self { connection } + } + + fn get_insert_condition(&self, record: &CardanoTransactionRecord) -> StdResult { + let expression = + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3)"; + let parameters = vec![ + Value::String(record.transaction_hash.clone()), + Value::Integer(record.block_number.try_into()?), + Value::Integer(record.immutable_file_number.try_into()?), + ]; + + Ok(WhereCondition::new(expression, parameters)) + } + + fn get_insert_many_condition( + &self, + transactions_records: Vec, + ) -> WhereCondition { + let columns = "(transaction_hash, block_number, immutable_file_number)"; + let values_columns: Vec<&str> = repeat("(?*, ?*, ?*)") + .take(transactions_records.len()) + .collect(); + + let values: Vec = transactions_records + .into_iter() + .flat_map(|record| { + vec![ + Value::String(record.transaction_hash), + Value::Integer(record.block_number.try_into().unwrap()), + Value::Integer(record.immutable_file_number.try_into().unwrap()), + ] + }) + .collect(); + + WhereCondition::new( + format!("{columns} values {}", values_columns.join(", ")).as_str(), + values, + ) + } +} + +impl<'client> Provider<'client> for InsertCardanoTransactionProvider<'client> { + type Entity = CardanoTransactionRecord; + + fn get_connection(&'client self) -> &'client SqliteConnection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!("insert or ignore into cardano_tx {condition} returning {projection}") + } +} + +/// ## Cardano transaction repository +/// +/// This is a business oriented layer to perform actions on the database through +/// providers. +pub struct CardanoTransactionRepository { + connection: Arc, +} + +impl CardanoTransactionRepository { + /// Instantiate service + pub fn new(connection: Arc) -> Self { + Self { connection } + } + + /// Return the [CardanoTransactionRecord] for the given transaction hash. + pub async fn get_transaction( + &self, + transaction_hash: &TransactionHash, + ) -> StdResult> { + let provider = CardanoTransactionProvider::new(&self.connection); + let filters = provider.get_transaction_hash_condition(transaction_hash); + let mut transactions = provider.find(filters)?; + + Ok(transactions.next()) + } + + /// Create a new [CardanoTransactionRecord] in the database. + pub async fn create_transaction( + &self, + transaction_hash: &TransactionHash, + block_number: BlockNumber, + immutable_file_number: ImmutableFileNumber, + ) -> StdResult> { + let provider = InsertCardanoTransactionProvider::new(&self.connection); + let filters = provider.get_insert_condition(&CardanoTransactionRecord { + transaction_hash: transaction_hash.to_owned(), + block_number, + immutable_file_number, + })?; + let mut cursor = provider.find(filters)?; + + Ok(cursor.next()) + } + + /// Create new [CardanoTransactionRecord]s in the database. + pub async fn create_transactions( + &self, + transactions: Vec, + ) -> StdResult> { + let provider = InsertCardanoTransactionProvider::new(&self.connection); + let filters = provider.get_insert_many_condition(transactions); + let cursor = provider.find(filters)?; + + Ok(cursor.collect()) + } +} + +#[async_trait] +impl TransactionStore for CardanoTransactionRepository { + async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()> { + let records: Vec = + transactions.iter().map(|tx| tx.to_owned().into()).collect(); + self.create_transactions(records) + .await + .with_context(|| "CardanoTransactionRepository can not store transactions")?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::sqlite::SourceAlias; + use sqlite::Connection; + + use crate::{Configuration, ProductionServiceBuilder, SQLITE_FILE_CARDANO_TRANSACTIONS}; + + use super::*; + + async fn get_connection() -> Arc { + let party_id = "party-id-123".to_string(); + let configuration = Configuration::new_sample(&party_id); + let production_service_builder = ProductionServiceBuilder::new(&configuration); + production_service_builder + .build_sqlite_connection( + SQLITE_FILE_CARDANO_TRANSACTIONS, + crate::database::cardano_transactions_migration::get_migrations(), + ) + .await + .unwrap() + } + + #[test] + fn cardano_transactions_projection() { + let projection = CardanoTransactionRecord::get_projection(); + let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); + + assert_eq!( + "cardano_tx.transaction_hash as transaction_hash, cardano_tx.block_number as block_number, cardano_tx.immutable_file_number as immutable_file_number".to_string(), + projection.expand(aliases) + ) + } + + #[test] + fn provider_transaction_hash_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = CardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_transaction_hash_condition( + &"0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string(), + ) + .expand(); + + assert_eq!("transaction_hash = ?1".to_string(), expr); + assert_eq!( + vec![Value::String( + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string() + )], + params, + ); + } + + #[test] + fn insert_provider_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = InsertCardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_insert_condition(&CardanoTransactionRecord { + transaction_hash: + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string(), + block_number: 10, + immutable_file_number: 99, + }) + .unwrap() + .expand(); + + assert_eq!( + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3)" + .to_string(), + expr + ); + assert_eq!( + vec![ + Value::String( + "0405a78c637f5c637e3146e293c0045ea80a07fac8f245901e7b491182931650".to_string() + ), + Value::Integer(10), + Value::Integer(99) + ], + params + ); + } + + #[test] + fn insert_provider_many_condition() { + let connection = Connection::open_thread_safe(":memory:").unwrap(); + let provider = InsertCardanoTransactionProvider::new(&connection); + let (expr, params) = provider + .get_insert_many_condition(vec![ + CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }, + CardanoTransactionRecord { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }, + ]) + .expand(); + + assert_eq!( + "(transaction_hash, block_number, immutable_file_number) values (?1, ?2, ?3), (?4, ?5, ?6)" + .to_string(), + expr + ); + assert_eq!( + vec![ + Value::String("tx-hash-123".to_string()), + Value::Integer(10), + Value::Integer(99), + Value::String("tx-hash-456".to_string()), + Value::Integer(11), + Value::Integer(100) + ], + params + ); + } + + #[tokio::test] + async fn repository_create_and_get_transaction() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + repository + .create_transaction(&"tx-hash-123".to_string(), 10, 99) + .await + .unwrap(); + repository + .create_transaction(&"tx-hash-456".to_string(), 11, 100) + .await + .unwrap(); + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + } + + #[tokio::test] + async fn repository_create_ignore_further_transactions_when_exists() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + repository + .create_transaction(&"tx-hash-123".to_string(), 10, 99) + .await + .unwrap(); + repository + .create_transaction(&"tx-hash-123".to_string(), 11, 100) + .await + .unwrap(); + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + } + + #[tokio::test] + async fn repository_store_transactions_and_get_stored_transactions() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + + let cardano_transactions = vec![ + CardanoTransaction { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }, + CardanoTransaction { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }, + ]; + repository + .store_transactions(&cardano_transactions) + .await + .unwrap(); + + let transaction_result = repository + .get_transaction(&"tx-hash-123".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99 + }), + transaction_result + ); + + let transaction_result = repository + .get_transaction(&"tx-hash-456".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-456".to_string(), + block_number: 11, + immutable_file_number: 100, + }), + transaction_result + ); + } + + #[tokio::test] + async fn repository_store_transactions_doesnt_erase_existing_data() { + let connection = get_connection().await; + let repository = CardanoTransactionRepository::new(connection.clone()); + + repository + .create_transaction(&"tx-hash-000".to_string(), 1, 9) + .await + .unwrap(); + + let cardano_transactions = vec![CardanoTransaction { + transaction_hash: "tx-hash-123".to_string(), + block_number: 10, + immutable_file_number: 99, + }]; + repository + .store_transactions(&cardano_transactions) + .await + .unwrap(); + + let transaction_result = repository + .get_transaction(&"tx-hash-000".to_string()) + .await + .unwrap(); + + assert_eq!( + Some(CardanoTransactionRecord { + transaction_hash: "tx-hash-000".to_string(), + block_number: 1, + immutable_file_number: 9 + }), + transaction_result + ); + } +} diff --git a/mithril-signer/src/database/provider/mod.rs b/mithril-signer/src/database/provider/mod.rs new file mode 100644 index 00000000000..9942fcb1aff --- /dev/null +++ b/mithril-signer/src/database/provider/mod.rs @@ -0,0 +1,5 @@ +//! Signer related database providers + +mod cardano_transactions; + +pub use cardano_transactions::*; diff --git a/mithril-signer/src/lib.rs b/mithril-signer/src/lib.rs index e4d4d117aa0..6acdab48dbc 100644 --- a/mithril-signer/src/lib.rs +++ b/mithril-signer/src/lib.rs @@ -13,6 +13,7 @@ mod message_adapters; mod protocol_initializer_store; mod runtime; mod single_signer; +mod transaction_store; #[cfg(test)] pub use aggregator_client::dumb::DumbAggregatorClient; @@ -27,3 +28,7 @@ pub use single_signer::*; /// HTTP request timeout duration in milliseconds const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; + +/// SQLite file names +const SQLITE_FILE: &str = "signer.sqlite3"; +const SQLITE_FILE_CARDANO_TRANSACTIONS: &str = "cardano-transactions.sqlite3"; diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs index f309efd034c..27564e794dd 100644 --- a/mithril-signer/src/runtime/signer_services.rs +++ b/mithril-signer/src/runtime/signer_services.rs @@ -7,7 +7,7 @@ use mithril_common::{ api_version::APIVersionProvider, chain_observer::{CardanoCliRunner, ChainObserver, ChainObserverBuilder, ChainObserverType}, crypto_helper::{OpCert, ProtocolPartyId, SerDeShelleyFileFormat}, - database::{ApplicationNodeType, DatabaseVersionChecker}, + database::{ApplicationNodeType, DatabaseVersionChecker, SqlMigration}, digesters::{ cache::{ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProviderBuilder}, ImmutableFileObserver, @@ -21,13 +21,14 @@ use mithril_common::{ }, sqlite::SqliteConnection, store::{adapter::SQLiteAdapter, StakeStore}, - BeaconProvider, BeaconProviderImpl, DumbTransactionParser, StdResult, + BeaconProvider, BeaconProviderImpl, CardanoTransactionParser, StdResult, }; use crate::{ - aggregator_client::AggregatorClient, single_signer::SingleSigner, AggregatorHTTPClient, - Configuration, MithrilSingleSigner, ProtocolInitializerStore, ProtocolInitializerStorer, - HTTP_REQUEST_TIMEOUT_DURATION, + aggregator_client::AggregatorClient, database::provider::CardanoTransactionRepository, + single_signer::SingleSigner, AggregatorHTTPClient, Configuration, MithrilSingleSigner, + ProtocolInitializerStore, ProtocolInitializerStorer, HTTP_REQUEST_TIMEOUT_DURATION, + SQLITE_FILE, SQLITE_FILE_CARDANO_TRANSACTIONS, }; type StakeStoreService = Arc; @@ -156,8 +157,13 @@ impl<'a> ProductionServiceBuilder<'a> { Ok(Some(Arc::new(cache_provider))) } - async fn build_sqlite_connection(&self) -> StdResult> { - let sqlite_db_path = self.config.get_sqlite_file()?; + /// Build a SQLite connection. + pub async fn build_sqlite_connection( + &self, + sqlite_file_name: &str, + migrations: Vec, + ) -> StdResult> { + let sqlite_db_path = self.config.get_sqlite_file(sqlite_file_name)?; let sqlite_connection = Arc::new(Connection::open_thread_safe(sqlite_db_path)?); let mut db_checker = DatabaseVersionChecker::new( slog_scope::logger(), @@ -165,7 +171,7 @@ impl<'a> ProductionServiceBuilder<'a> { &sqlite_connection, ); - for migration in crate::database::migration::get_migrations() { + for migration in migrations { db_checker.add_migration(migration); } @@ -191,7 +197,15 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { })?; } - let sqlite_connection = self.build_sqlite_connection().await?; + let sqlite_connection = self + .build_sqlite_connection(SQLITE_FILE, crate::database::migration::get_migrations()) + .await?; + let cardano_transactions_sqlite_connection = self + .build_sqlite_connection( + SQLITE_FILE_CARDANO_TRANSACTIONS, + crate::database::cardano_transactions_migration::get_migrations(), + ) + .await?; let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( Box::new(SQLiteAdapter::new( @@ -250,10 +264,13 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { )); let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); - let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); + let transaction_parser = Arc::new(CardanoTransactionParser::default()); + let transaction_store = Arc::new(CardanoTransactionRepository::new( + cardano_transactions_sqlite_connection, + )); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser, - todo!(), + transaction_store, &self.config.db_directory, slog_scope::logger(), )); diff --git a/mithril-signer/src/transaction_store.rs b/mithril-signer/src/transaction_store.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 7dd7fda2da7..357d19b5309 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -1,29 +1,26 @@ #![allow(dead_code)] use slog::Drain; use slog_scope::debug; -use std::{fmt::Debug, path::Path, path::PathBuf, sync::Arc, time::Duration}; +use std::{fmt::Debug, path::Path, sync::Arc, time::Duration}; use thiserror::Error; use mithril_common::{ api_version::APIVersionProvider, chain_observer::FakeObserver, - crypto_helper::tests_setup, digesters::{DumbImmutableDigester, DumbImmutableFileObserver, ImmutableFileObserver}, entities::{Beacon, Epoch, SignerWithStake}, - era::{ - adapters::{EraReaderAdapterType, EraReaderDummyAdapter}, - EraChecker, EraMarker, EraReader, SupportedEra, - }, + era::{adapters::EraReaderDummyAdapter, EraChecker, EraMarker, EraReader, SupportedEra}, signable_builder::{ CardanoImmutableFilesFullSignableBuilder, CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, MithrilStakeDistributionSignableBuilder, }, - store::{adapter::MemoryAdapter, StakeStore, StakeStorer, TransactionStore}, + store::{adapter::MemoryAdapter, StakeStore, StakeStorer}, BeaconProvider, BeaconProviderImpl, DumbTransactionParser, StdError, }; use mithril_signer::{ - AggregatorClient, Configuration, MithrilSingleSigner, ProtocolInitializerStore, + database::provider::CardanoTransactionRepository, AggregatorClient, Configuration, + MithrilSingleSigner, ProductionServiceBuilder, ProtocolInitializerStore, ProtocolInitializerStorer, RuntimeError, SignerRunner, SignerServices, SignerState, StateMachine, }; @@ -74,31 +71,16 @@ impl StateMachineTester { TestError::AssertFailed("there should be at least one signer with stakes".to_string()) })?; let selected_signer_party_id = selected_signer_with_stake.party_id.clone(); - let selected_signer_temp_dir = - tests_setup::setup_temp_directory_for_signer(&selected_signer_party_id, false); - let config = Configuration { - aggregator_endpoint: "http://0.0.0.0:8000".to_string(), - relay_endpoint: None, - cardano_cli_path: PathBuf::new(), - cardano_node_socket_path: PathBuf::new(), - db_directory: PathBuf::new(), - network: "devnet".to_string(), - network_magic: Some(42), - party_id: Some(selected_signer_party_id), - run_interval: 5000, - data_stores_directory: PathBuf::new(), - store_retention_limit: None, - kes_secret_key_path: selected_signer_temp_dir - .as_ref() - .map(|dir| dir.join("kes.sk")), - operational_certificate_path: selected_signer_temp_dir - .as_ref() - .map(|dir| dir.join("opcert.cert")), - disable_digests_cache: false, - reset_digests_cache: false, - era_reader_adapter_type: EraReaderAdapterType::Bootstrap, - era_reader_adapter_params: None, - }; + let config = Configuration::new_sample(&selected_signer_party_id); + + let production_service_builder = ProductionServiceBuilder::new(&config); + let cardano_transactions_sqlite_connection = production_service_builder + .build_sqlite_connection( + ":memory:", + mithril_signer::database::cardano_transactions_migration::get_migrations(), + ) + .await + .unwrap(); let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter); let drain = slog_term::CompactFormat::new(decorator).build().fuse(); @@ -158,7 +140,9 @@ impl StateMachineTester { let mithril_stake_distribution_signable_builder = Arc::new(MithrilStakeDistributionSignableBuilder::default()); let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); - let transaction_store: Arc = todo!(); + let transaction_store = Arc::new(CardanoTransactionRepository::new( + cardano_transactions_sqlite_connection, + )); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser.clone(), transaction_store.clone(), From 12d37be91ab5377fa199b0269f345c64b143f0ab Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 16:28:59 +0100 Subject: [PATCH 13/25] fix: move transaction store trait to signable builder To allow compilation of the client library. --- .../database/provider/cardano_transactions.rs | 2 +- .../src/dependency_injection/builder.rs | 6 ++---- .../src/dependency_injection/containers.rs | 4 ++-- .../src/signable_builder/cardano_transactions.rs | 16 +++++++++++++--- mithril-common/src/signable_builder/mod.rs | 4 ++++ mithril-common/src/store/mod.rs | 4 ---- mithril-common/src/store/transaction_store.rs | 13 ------------- .../database/provider/cardano_transactions.rs | 2 +- mithril-signer/src/lib.rs | 1 - mithril-signer/src/runtime/runner.rs | 3 ++- mithril-signer/src/transaction_store.rs | 0 11 files changed, 25 insertions(+), 30 deletions(-) delete mode 100644 mithril-common/src/store/transaction_store.rs delete mode 100644 mithril-signer/src/transaction_store.rs diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transactions.rs index d24ea288705..8ad49c670a3 100644 --- a/mithril-aggregator/src/database/provider/cardano_transactions.rs +++ b/mithril-aggregator/src/database/provider/cardano_transactions.rs @@ -1,10 +1,10 @@ use mithril_common::{ entities::{BlockNumber, CardanoTransaction, ImmutableFileNumber, TransactionHash}, + signable_builder::TransactionStore, sqlite::{ HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition, }, - store::TransactionStore, StdResult, }; diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 5840331916a..82ef235676b 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -37,12 +37,10 @@ use mithril_common::{ }, signable_builder::{ CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, SignableBuilderService, - }, - sqlite::SqliteConnection, - store::{ - adapter::{MemoryAdapter, SQLiteAdapter, StoreAdapter}, TransactionStore, }, + sqlite::SqliteConnection, + store::adapter::{MemoryAdapter, SQLiteAdapter, StoreAdapter}, BeaconProvider, BeaconProviderImpl, CardanoTransactionParser, }; diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index eb3ae4a0d52..28b59327666 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -10,9 +10,9 @@ use mithril_common::{ digesters::{ImmutableDigester, ImmutableFileObserver}, entities::{Epoch, ProtocolParameters, SignerWithStake, StakeDistribution}, era::{EraChecker, EraReader}, - signable_builder::SignableBuilderService, + signable_builder::{SignableBuilderService, TransactionStore}, sqlite::SqliteConnection, - store::{StakeStorer, TransactionStore}, + store::StakeStorer, test_utils::MithrilFixture, BeaconProvider, }; diff --git a/mithril-common/src/signable_builder/cardano_transactions.rs b/mithril-common/src/signable_builder/cardano_transactions.rs index 782b4a1ad2c..c593c2f89e9 100644 --- a/mithril-common/src/signable_builder/cardano_transactions.rs +++ b/mithril-common/src/signable_builder/cardano_transactions.rs @@ -8,12 +8,22 @@ use slog::{debug, Logger}; use crate::{ cardano_transactions_parser::TransactionParser, - entities::{Beacon, ProtocolMessage, ProtocolMessagePartKey}, + entities::{Beacon, CardanoTransaction, ProtocolMessage, ProtocolMessagePartKey}, signable_builder::SignableBuilder, - store::TransactionStore, StdResult, }; +#[cfg(test)] +use mockall::automock; + +/// Cardano transactions store +#[cfg_attr(test, automock)] +#[async_trait] +pub trait TransactionStore: Send + Sync { + /// Store list of transactions + async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()>; +} + /// A [CardanoTransactionsSignableBuilder] builder pub struct CardanoTransactionsSignableBuilder { transaction_parser: Arc, @@ -78,7 +88,7 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { #[cfg(test)] mod tests { use crate::cardano_transactions_parser::DumbTransactionParser; - use crate::store::MockTransactionStore; + use crate::signable_builder::MockTransactionStore; use super::*; use slog::Drain; diff --git a/mithril-common/src/signable_builder/mod.rs b/mithril-common/src/signable_builder/mod.rs index daf173223e8..377c62a42c9 100644 --- a/mithril-common/src/signable_builder/mod.rs +++ b/mithril-common/src/signable_builder/mod.rs @@ -15,3 +15,7 @@ pub use cardano_transactions::*; pub use interface::*; pub use mithril_stake_distribution::*; pub use signable_builder_service::*; + +#[cfg(test)] +pub use cardano_transactions::MockTransactionStore; +pub use cardano_transactions::TransactionStore; diff --git a/mithril-common/src/store/mod.rs b/mithril-common/src/store/mod.rs index 5ec5574fd52..ba017e2e98f 100644 --- a/mithril-common/src/store/mod.rs +++ b/mithril-common/src/store/mod.rs @@ -4,10 +4,6 @@ pub mod adapter; mod stake_store; mod store_pruner; -mod transaction_store; pub use stake_store::{StakeStore, StakeStorer}; pub use store_pruner::StorePruner; -#[cfg(test)] -pub use transaction_store::MockTransactionStore; -pub use transaction_store::TransactionStore; diff --git a/mithril-common/src/store/transaction_store.rs b/mithril-common/src/store/transaction_store.rs deleted file mode 100644 index a251b800af4..00000000000 --- a/mithril-common/src/store/transaction_store.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::{entities::CardanoTransaction, StdResult}; -use async_trait::async_trait; - -#[cfg(test)] -use mockall::automock; - -/// Cardano transactions store -#[cfg_attr(test, automock)] -#[async_trait] -pub trait TransactionStore: Send + Sync { - /// Store list of transactions - async fn store_transactions(&self, transactions: &[CardanoTransaction]) -> StdResult<()>; -} diff --git a/mithril-signer/src/database/provider/cardano_transactions.rs b/mithril-signer/src/database/provider/cardano_transactions.rs index a765b81a339..9bd1e2b3677 100644 --- a/mithril-signer/src/database/provider/cardano_transactions.rs +++ b/mithril-signer/src/database/provider/cardano_transactions.rs @@ -1,10 +1,10 @@ use mithril_common::{ entities::{BlockNumber, CardanoTransaction, ImmutableFileNumber, TransactionHash}, + signable_builder::TransactionStore, sqlite::{ HydrationError, Projection, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition, }, - store::TransactionStore, StdResult, }; diff --git a/mithril-signer/src/lib.rs b/mithril-signer/src/lib.rs index 6acdab48dbc..d309ec70dda 100644 --- a/mithril-signer/src/lib.rs +++ b/mithril-signer/src/lib.rs @@ -13,7 +13,6 @@ mod message_adapters; mod protocol_initializer_store; mod runtime; mod single_signer; -mod transaction_store; #[cfg(test)] pub use aggregator_client::dumb::DumbAggregatorClient; diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index f13e3ffa23b..f5a8d39cf75 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -462,10 +462,11 @@ mod tests { signable_builder::{ CardanoImmutableFilesFullSignableBuilder, CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, MithrilStakeDistributionSignableBuilder, + TransactionStore, }, store::{ adapter::{DumbStoreAdapter, MemoryAdapter}, - StakeStore, StakeStorer, TransactionStore, + StakeStore, StakeStorer, }, test_utils::{fake_data, MithrilFixtureBuilder}, BeaconProvider, BeaconProviderImpl, CardanoNetwork, DumbTransactionParser, diff --git a/mithril-signer/src/transaction_store.rs b/mithril-signer/src/transaction_store.rs deleted file mode 100644 index e69de29bb2d..00000000000 From 34a86f36e1c7d468fc27bc37ebbe3fdf3ceb4442 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 17:43:31 +0100 Subject: [PATCH 14/25] fix: add missing feature for protocol demo --- demo/protocol-demo/Cargo.toml | 2 +- mithril-common/src/signable_builder/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/protocol-demo/Cargo.toml b/demo/protocol-demo/Cargo.toml index 7a76ad036ce..fe720a8963a 100644 --- a/demo/protocol-demo/Cargo.toml +++ b/demo/protocol-demo/Cargo.toml @@ -14,7 +14,7 @@ blake2 = "0.10.6" clap = { version = "4.4.6", features = ["derive"] } hex = "0.4.3" log = "0.4.20" -mithril-common = { path = "../../mithril-common" } +mithril-common = { path = "../../mithril-common", features = ["fs"] } rand_chacha = "0.3.1" rand_core = "0.6.4" serde = { version = "1.0.188", features = ["derive"] } diff --git a/mithril-common/src/signable_builder/mod.rs b/mithril-common/src/signable_builder/mod.rs index 377c62a42c9..3050216fff4 100644 --- a/mithril-common/src/signable_builder/mod.rs +++ b/mithril-common/src/signable_builder/mod.rs @@ -16,6 +16,6 @@ pub use interface::*; pub use mithril_stake_distribution::*; pub use signable_builder_service::*; -#[cfg(test)] +#[cfg(all(test, feature = "fs"))] pub use cardano_transactions::MockTransactionStore; pub use cardano_transactions::TransactionStore; From df5249aa31cc943932ef06feafa02923f7d84b93 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 17:43:52 +0100 Subject: [PATCH 15/25] fix: integration tests in aggregator --- mithril-aggregator/src/dependency_injection/builder.rs | 10 +++++++++- mithril-aggregator/tests/certificate_chain.rs | 2 +- mithril-aggregator/tests/create_certificate.rs | 2 +- mithril-aggregator/tests/era_checker.rs | 2 +- mithril-aggregator/tests/genesis_to_signing.rs | 2 +- mithril-aggregator/tests/open_message_expiration.rs | 2 +- mithril-aggregator/tests/open_message_newer_exists.rs | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 82ef235676b..f0eb811420b 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -264,7 +264,15 @@ impl DependenciesBuilder { ExecutionEnvironment::Production => { self.configuration.get_sqlite_dir().join(sqlite_file_name) } - _ => self.configuration.data_stores_directory.clone(), + _ => { + if self.configuration.data_stores_directory.to_string_lossy() == ":memory:" { + self.configuration.data_stores_directory.clone() + } else { + self.configuration + .data_stores_directory + .join(sqlite_file_name) + } + } }; let connection = Connection::open_thread_safe(&path) diff --git a/mithril-aggregator/tests/certificate_chain.rs b/mithril-aggregator/tests/certificate_chain.rs index b5f85ca945e..ce111993ed7 100644 --- a/mithril-aggregator/tests/certificate_chain.rs +++ b/mithril-aggregator/tests/certificate_chain.rs @@ -19,7 +19,7 @@ async fn certificate_chain() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("certificate_chain").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("certificate_chain"), ..Configuration::new_sample() }; let mut tester = diff --git a/mithril-aggregator/tests/create_certificate.rs b/mithril-aggregator/tests/create_certificate.rs index 2e28d416285..ecfda3cf7cb 100644 --- a/mithril-aggregator/tests/create_certificate.rs +++ b/mithril-aggregator/tests/create_certificate.rs @@ -19,7 +19,7 @@ async fn create_certificate() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("create_certificate").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("create_certificate"), ..Configuration::new_sample() }; let mut tester = diff --git a/mithril-aggregator/tests/era_checker.rs b/mithril-aggregator/tests/era_checker.rs index ba7edbb7f10..96c7945d09d 100644 --- a/mithril-aggregator/tests/era_checker.rs +++ b/mithril-aggregator/tests/era_checker.rs @@ -20,7 +20,7 @@ async fn testing_eras() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("testing_eras").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("testing_eras"), ..Configuration::new_sample() }; let mut tester = diff --git a/mithril-aggregator/tests/genesis_to_signing.rs b/mithril-aggregator/tests/genesis_to_signing.rs index 7455ebd8e3f..bcfdd13dee9 100644 --- a/mithril-aggregator/tests/genesis_to_signing.rs +++ b/mithril-aggregator/tests/genesis_to_signing.rs @@ -16,7 +16,7 @@ async fn genesis_to_signing() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("genesis_to_signing").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("genesis_to_signing"), ..Configuration::new_sample() }; let mut tester = diff --git a/mithril-aggregator/tests/open_message_expiration.rs b/mithril-aggregator/tests/open_message_expiration.rs index bb2b948463f..6bc7eea3aec 100644 --- a/mithril-aggregator/tests/open_message_expiration.rs +++ b/mithril-aggregator/tests/open_message_expiration.rs @@ -21,7 +21,7 @@ async fn open_message_expiration() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("create_certificate").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("create_certificate"), ..Configuration::new_sample() }; let mut tester = diff --git a/mithril-aggregator/tests/open_message_newer_exists.rs b/mithril-aggregator/tests/open_message_newer_exists.rs index b0f90494f18..1e13e896e5a 100644 --- a/mithril-aggregator/tests/open_message_newer_exists.rs +++ b/mithril-aggregator/tests/open_message_newer_exists.rs @@ -19,7 +19,7 @@ async fn open_message_newer_exists() { }; let configuration = Configuration { protocol_parameters: protocol_parameters.clone(), - data_stores_directory: get_test_dir("create_certificate").join("aggregator.sqlite3"), + data_stores_directory: get_test_dir("create_certificate"), ..Configuration::new_sample() }; let mut tester = From 0c4e201b2313e2fb5e1a69497bb25fe16f41d65e Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 17:46:54 +0100 Subject: [PATCH 16/25] fix: database tests lock file problem in signer --- mithril-signer/src/database/provider/cardano_transactions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mithril-signer/src/database/provider/cardano_transactions.rs b/mithril-signer/src/database/provider/cardano_transactions.rs index 9bd1e2b3677..879717a5089 100644 --- a/mithril-signer/src/database/provider/cardano_transactions.rs +++ b/mithril-signer/src/database/provider/cardano_transactions.rs @@ -247,7 +247,7 @@ mod tests { use mithril_common::sqlite::SourceAlias; use sqlite::Connection; - use crate::{Configuration, ProductionServiceBuilder, SQLITE_FILE_CARDANO_TRANSACTIONS}; + use crate::{Configuration, ProductionServiceBuilder}; use super::*; @@ -257,7 +257,7 @@ mod tests { let production_service_builder = ProductionServiceBuilder::new(&configuration); production_service_builder .build_sqlite_connection( - SQLITE_FILE_CARDANO_TRANSACTIONS, + ":memory:", crate::database::cardano_transactions_migration::get_migrations(), ) .await From fd5d4e02f0ea10e54b02f4545000d72a716edc6c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 18:09:19 +0100 Subject: [PATCH 17/25] fix: client wasm compilation failed Becasue of incorrect feature configuration. --- mithril-common/src/signable_builder/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril-common/src/signable_builder/mod.rs b/mithril-common/src/signable_builder/mod.rs index 3050216fff4..c7914eb455e 100644 --- a/mithril-common/src/signable_builder/mod.rs +++ b/mithril-common/src/signable_builder/mod.rs @@ -18,4 +18,5 @@ pub use signable_builder_service::*; #[cfg(all(test, feature = "fs"))] pub use cardano_transactions::MockTransactionStore; +#[cfg(all(test, feature = "fs"))] pub use cardano_transactions::TransactionStore; From 03a924394e0a3e4deb06d4fa640cbf4c82b27c29 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 24 Jan 2024 10:07:49 +0100 Subject: [PATCH 18/25] chore: harmonize Cardano transaction namings --- ...on.rs => cardano_transaction_migration.rs} | 2 +- mithril-aggregator/src/database/mod.rs | 2 +- ...transactions.rs => cardano_transaction.rs} | 4 +-- .../src/database/provider/mod.rs | 4 +-- .../src/dependency_injection/builder.rs | 32 +++++++++---------- .../src/dependency_injection/containers.rs | 8 ++--- ...arser.rs => cardano_transaction_parser.rs} | 2 +- mithril-common/src/lib.rs | 4 +-- .../signable_builder/cardano_transactions.rs | 4 +-- ...on.rs => cardano_transaction_migration.rs} | 2 +- mithril-signer/src/database/mod.rs | 2 +- ...transactions.rs => cardano_transaction.rs} | 4 +-- mithril-signer/src/database/provider/mod.rs | 4 +-- mithril-signer/src/lib.rs | 2 +- mithril-signer/src/runtime/signer_services.rs | 10 +++--- .../test_extensions/state_machine_tester.rs | 6 ++-- 16 files changed, 45 insertions(+), 47 deletions(-) rename mithril-aggregator/src/database/{cardano_transactions_migration.rs => cardano_transaction_migration.rs} (87%) rename mithril-aggregator/src/database/provider/{cardano_transactions.rs => cardano_transaction.rs} (99%) rename mithril-common/src/{cardano_transactions_parser.rs => cardano_transaction_parser.rs} (99%) rename mithril-signer/src/database/{cardano_transactions_migration.rs => cardano_transaction_migration.rs} (87%) rename mithril-signer/src/database/provider/{cardano_transactions.rs => cardano_transaction.rs} (99%) diff --git a/mithril-aggregator/src/database/cardano_transactions_migration.rs b/mithril-aggregator/src/database/cardano_transaction_migration.rs similarity index 87% rename from mithril-aggregator/src/database/cardano_transactions_migration.rs rename to mithril-aggregator/src/database/cardano_transaction_migration.rs index 116d8ea9933..73d4861990a 100644 --- a/mithril-aggregator/src/database/cardano_transactions_migration.rs +++ b/mithril-aggregator/src/database/cardano_transaction_migration.rs @@ -19,7 +19,7 @@ create table cardano_tx ( primary key (transaction_hash) ); -create unique index cardano_transactions_unique_index on cardano_tx(immutable_file_number); +create unique index cardano_tx_immutable_file_number_index on cardano_tx(immutable_file_number); "#, ), ] diff --git a/mithril-aggregator/src/database/mod.rs b/mithril-aggregator/src/database/mod.rs index ff8a5d73d29..82729bf9fd9 100644 --- a/mithril-aggregator/src/database/mod.rs +++ b/mithril-aggregator/src/database/mod.rs @@ -2,6 +2,6 @@ //! This module contains the entities definition tied with database //! representation with their associated providers. -pub mod cardano_transactions_migration; +pub mod cardano_transaction_migration; pub mod migration; pub mod provider; diff --git a/mithril-aggregator/src/database/provider/cardano_transactions.rs b/mithril-aggregator/src/database/provider/cardano_transaction.rs similarity index 99% rename from mithril-aggregator/src/database/provider/cardano_transactions.rs rename to mithril-aggregator/src/database/provider/cardano_transaction.rs index 8ad49c670a3..3695572f72d 100644 --- a/mithril-aggregator/src/database/provider/cardano_transactions.rs +++ b/mithril-aggregator/src/database/provider/cardano_transaction.rs @@ -255,13 +255,13 @@ mod tests { let config = Configuration::new_sample(); let mut builder = DependenciesBuilder::new(config); builder - .get_sqlite_connection_cardano_transactions() + .get_sqlite_connection_cardano_transaction() .await .unwrap() } #[test] - fn cardano_transactions_projection() { + fn cardano_transaction_projection() { let projection = CardanoTransactionRecord::get_projection(); let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); diff --git a/mithril-aggregator/src/database/provider/mod.rs b/mithril-aggregator/src/database/provider/mod.rs index b2165c64d54..c729406ff7f 100644 --- a/mithril-aggregator/src/database/provider/mod.rs +++ b/mithril-aggregator/src/database/provider/mod.rs @@ -1,5 +1,5 @@ //! Aggregator related database providers -mod cardano_transactions; +mod cardano_transaction; mod certificate; mod epoch_setting; mod open_message; @@ -11,7 +11,7 @@ mod stake_pool; #[cfg(test)] mod test_helper; -pub use cardano_transactions::*; +pub use cardano_transaction::*; pub use certificate::*; pub use epoch_setting::*; pub use open_message::*; diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index f0eb811420b..82dabcf57dd 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -15,7 +15,7 @@ use warp::Filter; use mithril_common::{ api_version::APIVersionProvider, - cardano_transactions_parser::TransactionParser, + cardano_transaction_parser::TransactionParser, certificate_chain::{CertificateVerifier, MithrilCertificateVerifier}, chain_observer::{CardanoCliRunner, ChainObserver, ChainObserverBuilder, FakeObserver}, crypto_helper::{ @@ -73,7 +73,7 @@ use crate::{ use super::{DependenciesBuilderError, EpochServiceWrapper, Result}; const SQLITE_FILE: &str = "aggregator.sqlite3"; -const SQLITE_FILE_CARDANO_TRANSACTIONS: &str = "cardano-transactions.sqlite3"; +const SQLITE_FILE_CARDANO_TRANSACTION: &str = "cardano-transaction.sqlite3"; /// ## Dependencies container builder /// @@ -94,7 +94,7 @@ pub struct DependenciesBuilder { pub sqlite_connection: Option>, /// Cardano transactions SQLite database connection - pub cardano_transactions_sqlite_connection: Option>, + pub transaction_sqlite_connection: Option>, /// Stake Store used by the StakeDistributionService /// It shall be a private dependency. @@ -215,7 +215,7 @@ impl DependenciesBuilder { Self { configuration, sqlite_connection: None, - cardano_transactions_sqlite_connection: None, + transaction_sqlite_connection: None, stake_store: None, snapshot_uploader: None, multi_signer: None, @@ -324,7 +324,7 @@ impl DependenciesBuilder { let _ = connection.execute("pragma analysis_limit=400; pragma optimize;"); } - if let Some(connection) = &self.cardano_transactions_sqlite_connection { + if let Some(connection) = &self.transaction_sqlite_connection { let _ = connection.execute("pragma analysis_limit=400; pragma optimize;"); } } @@ -345,21 +345,21 @@ impl DependenciesBuilder { } /// Get SQLite connection for the cardano transactions store - pub async fn get_sqlite_connection_cardano_transactions( + pub async fn get_sqlite_connection_cardano_transaction( &mut self, ) -> Result> { - if self.cardano_transactions_sqlite_connection.is_none() { - self.cardano_transactions_sqlite_connection = Some( + if self.transaction_sqlite_connection.is_none() { + self.transaction_sqlite_connection = Some( self.build_sqlite_connection( - SQLITE_FILE_CARDANO_TRANSACTIONS, - crate::database::cardano_transactions_migration::get_migrations(), + SQLITE_FILE_CARDANO_TRANSACTION, + crate::database::cardano_transaction_migration::get_migrations(), ) .await?, ); } Ok(self - .cardano_transactions_sqlite_connection + .transaction_sqlite_connection .as_ref() .cloned() .unwrap()) @@ -679,7 +679,7 @@ impl DependenciesBuilder { async fn build_transaction_store(&mut self) -> Result> { let transaction_store = CardanoTransactionRepository::new( - self.get_sqlite_connection_cardano_transactions().await?, + self.get_sqlite_connection_cardano_transaction().await?, ); Ok(Arc::new(transaction_store)) @@ -1137,9 +1137,7 @@ impl DependenciesBuilder { let dependency_manager = DependencyContainer { config: self.configuration.clone(), sqlite_connection: self.get_sqlite_connection().await?, - sqlite_connection_cardano_transactions: self - .get_sqlite_connection_cardano_transactions() - .await?, + sqlite_connection_transaction: self.get_sqlite_connection_cardano_transaction().await?, stake_store: self.get_stake_store().await?, snapshot_uploader: self.get_snapshot_uploader().await?, multi_signer: self.get_multi_signer().await?, @@ -1171,8 +1169,8 @@ impl DependenciesBuilder { signed_entity_storer: self.get_signed_entity_storer().await?, signer_getter: self.get_signer_store().await?, message_service: self.get_message_service().await?, - cardano_transactions_parser: self.get_transaction_parser().await?, - cardano_transactions_store: self.get_transaction_store().await?, + transaction_parser: self.get_transaction_parser().await?, + transaction_store: self.get_transaction_store().await?, }; Ok(dependency_manager) diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index 28b59327666..8c4e64c06bb 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -3,7 +3,7 @@ use tokio::sync::RwLock; use mithril_common::{ api_version::APIVersionProvider, - cardano_transactions_parser::TransactionParser, + cardano_transaction_parser::TransactionParser, certificate_chain::CertificateVerifier, chain_observer::ChainObserver, crypto_helper::ProtocolGenesisVerifier, @@ -50,7 +50,7 @@ pub struct DependencyContainer { pub sqlite_connection: Arc, /// SQLite database connection for Cardano transactions - pub sqlite_connection_cardano_transactions: Arc, + pub sqlite_connection_transaction: Arc, /// Stake Store used by the StakeDistributionService /// It shall be a private dependency. @@ -84,10 +84,10 @@ pub struct DependencyContainer { pub beacon_provider: Arc, /// Cardano transactions store. - pub cardano_transactions_store: Arc, + pub transaction_store: Arc, /// Cardano transactions parser. - pub cardano_transactions_parser: Arc, + pub transaction_parser: Arc, /// Immutable file observer service. pub immutable_file_observer: Arc, diff --git a/mithril-common/src/cardano_transactions_parser.rs b/mithril-common/src/cardano_transaction_parser.rs similarity index 99% rename from mithril-common/src/cardano_transactions_parser.rs rename to mithril-common/src/cardano_transaction_parser.rs index c5a120a8f3d..3525a3ca0e5 100644 --- a/mithril-common/src/cardano_transactions_parser.rs +++ b/mithril-common/src/cardano_transaction_parser.rs @@ -21,7 +21,7 @@ use tokio::sync::RwLock; /// mod test { /// use anyhow::anyhow; /// use async_trait::async_trait; -/// use mithril_common::cardano_transactions_parser::TransactionParser; +/// use mithril_common::cardano_transaction_parser::TransactionParser; /// use mithril_common::entities::{Beacon, CardanoTransaction}; /// use mithril_common::StdResult; /// use mockall::mock; diff --git a/mithril-common/src/lib.rs b/mithril-common/src/lib.rs index b227cb10af8..cbd802d5b84 100644 --- a/mithril-common/src/lib.rs +++ b/mithril-common/src/lib.rs @@ -59,7 +59,7 @@ pub mod protocol; pub mod signable_builder; #[cfg(feature = "fs")] -pub mod cardano_transactions_parser; +pub mod cardano_transaction_parser; #[cfg(feature = "database")] pub mod sqlite; @@ -75,7 +75,7 @@ pub use beacon_provider::{BeaconProvider, BeaconProviderImpl}; pub use entities::{CardanoNetwork, MagicId}; #[cfg(feature = "fs")] -pub use cardano_transactions_parser::{ +pub use cardano_transaction_parser::{ CardanoTransactionParser, DumbTransactionParser, TransactionParser, }; diff --git a/mithril-common/src/signable_builder/cardano_transactions.rs b/mithril-common/src/signable_builder/cardano_transactions.rs index c593c2f89e9..5fdaea7111f 100644 --- a/mithril-common/src/signable_builder/cardano_transactions.rs +++ b/mithril-common/src/signable_builder/cardano_transactions.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use slog::{debug, Logger}; use crate::{ - cardano_transactions_parser::TransactionParser, + cardano_transaction_parser::TransactionParser, entities::{Beacon, CardanoTransaction, ProtocolMessage, ProtocolMessagePartKey}, signable_builder::SignableBuilder, StdResult, @@ -87,7 +87,7 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { #[cfg(test)] mod tests { - use crate::cardano_transactions_parser::DumbTransactionParser; + use crate::cardano_transaction_parser::DumbTransactionParser; use crate::signable_builder::MockTransactionStore; use super::*; diff --git a/mithril-signer/src/database/cardano_transactions_migration.rs b/mithril-signer/src/database/cardano_transaction_migration.rs similarity index 87% rename from mithril-signer/src/database/cardano_transactions_migration.rs rename to mithril-signer/src/database/cardano_transaction_migration.rs index 116d8ea9933..73d4861990a 100644 --- a/mithril-signer/src/database/cardano_transactions_migration.rs +++ b/mithril-signer/src/database/cardano_transaction_migration.rs @@ -19,7 +19,7 @@ create table cardano_tx ( primary key (transaction_hash) ); -create unique index cardano_transactions_unique_index on cardano_tx(immutable_file_number); +create unique index cardano_tx_immutable_file_number_index on cardano_tx(immutable_file_number); "#, ), ] diff --git a/mithril-signer/src/database/mod.rs b/mithril-signer/src/database/mod.rs index debe3352c7d..f33660e6328 100644 --- a/mithril-signer/src/database/mod.rs +++ b/mithril-signer/src/database/mod.rs @@ -1,6 +1,6 @@ //! database module. //! This module contains the entities definition tied with database //! representation with their associated providers. -pub mod cardano_transactions_migration; +pub mod cardano_transaction_migration; pub mod migration; pub mod provider; diff --git a/mithril-signer/src/database/provider/cardano_transactions.rs b/mithril-signer/src/database/provider/cardano_transaction.rs similarity index 99% rename from mithril-signer/src/database/provider/cardano_transactions.rs rename to mithril-signer/src/database/provider/cardano_transaction.rs index 879717a5089..c13b842b168 100644 --- a/mithril-signer/src/database/provider/cardano_transactions.rs +++ b/mithril-signer/src/database/provider/cardano_transaction.rs @@ -258,14 +258,14 @@ mod tests { production_service_builder .build_sqlite_connection( ":memory:", - crate::database::cardano_transactions_migration::get_migrations(), + crate::database::cardano_transaction_migration::get_migrations(), ) .await .unwrap() } #[test] - fn cardano_transactions_projection() { + fn cardano_transaction_projection() { let projection = CardanoTransactionRecord::get_projection(); let aliases = SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]); diff --git a/mithril-signer/src/database/provider/mod.rs b/mithril-signer/src/database/provider/mod.rs index 9942fcb1aff..174739c9610 100644 --- a/mithril-signer/src/database/provider/mod.rs +++ b/mithril-signer/src/database/provider/mod.rs @@ -1,5 +1,5 @@ //! Signer related database providers -mod cardano_transactions; +mod cardano_transaction; -pub use cardano_transactions::*; +pub use cardano_transaction::*; diff --git a/mithril-signer/src/lib.rs b/mithril-signer/src/lib.rs index d309ec70dda..150de5fc3e5 100644 --- a/mithril-signer/src/lib.rs +++ b/mithril-signer/src/lib.rs @@ -30,4 +30,4 @@ const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; /// SQLite file names const SQLITE_FILE: &str = "signer.sqlite3"; -const SQLITE_FILE_CARDANO_TRANSACTIONS: &str = "cardano-transactions.sqlite3"; +const SQLITE_FILE_CARDANO_TRANSACTION: &str = "cardano-transaction.sqlite3"; diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs index 27564e794dd..f28bd0cef52 100644 --- a/mithril-signer/src/runtime/signer_services.rs +++ b/mithril-signer/src/runtime/signer_services.rs @@ -28,7 +28,7 @@ use crate::{ aggregator_client::AggregatorClient, database::provider::CardanoTransactionRepository, single_signer::SingleSigner, AggregatorHTTPClient, Configuration, MithrilSingleSigner, ProtocolInitializerStore, ProtocolInitializerStorer, HTTP_REQUEST_TIMEOUT_DURATION, - SQLITE_FILE, SQLITE_FILE_CARDANO_TRANSACTIONS, + SQLITE_FILE, SQLITE_FILE_CARDANO_TRANSACTION, }; type StakeStoreService = Arc; @@ -200,10 +200,10 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { let sqlite_connection = self .build_sqlite_connection(SQLITE_FILE, crate::database::migration::get_migrations()) .await?; - let cardano_transactions_sqlite_connection = self + let transaction_sqlite_connection = self .build_sqlite_connection( - SQLITE_FILE_CARDANO_TRANSACTIONS, - crate::database::cardano_transactions_migration::get_migrations(), + SQLITE_FILE_CARDANO_TRANSACTION, + crate::database::cardano_transaction_migration::get_migrations(), ) .await?; @@ -266,7 +266,7 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> { Arc::new(MithrilStakeDistributionSignableBuilder::default()); let transaction_parser = Arc::new(CardanoTransactionParser::default()); let transaction_store = Arc::new(CardanoTransactionRepository::new( - cardano_transactions_sqlite_connection, + transaction_sqlite_connection, )); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser, diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 357d19b5309..85bbb56417e 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -74,10 +74,10 @@ impl StateMachineTester { let config = Configuration::new_sample(&selected_signer_party_id); let production_service_builder = ProductionServiceBuilder::new(&config); - let cardano_transactions_sqlite_connection = production_service_builder + let transaction_sqlite_connection = production_service_builder .build_sqlite_connection( ":memory:", - mithril_signer::database::cardano_transactions_migration::get_migrations(), + mithril_signer::database::cardano_transaction_migration::get_migrations(), ) .await .unwrap(); @@ -141,7 +141,7 @@ impl StateMachineTester { Arc::new(MithrilStakeDistributionSignableBuilder::default()); let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); let transaction_store = Arc::new(CardanoTransactionRepository::new( - cardano_transactions_sqlite_connection, + transaction_sqlite_connection, )); let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new( transaction_parser.clone(), From d0086369d8ecd1abb23afb8e1bb236a21b31de9c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 23 Jan 2024 17:53:55 +0100 Subject: [PATCH 19/25] chore: update crates versions --- Cargo.lock | 8 ++++---- demo/protocol-demo/Cargo.toml | 2 +- mithril-aggregator/Cargo.toml | 2 +- mithril-common/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2c59ba999b..080d25ae7bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3279,7 +3279,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.4.27" +version = "0.4.28" dependencies = [ "anyhow", "async-trait", @@ -3417,7 +3417,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.2.152" +version = "0.2.153" dependencies = [ "anyhow", "async-trait", @@ -3514,7 +3514,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.103" +version = "0.2.104" dependencies = [ "anyhow", "async-trait", @@ -3566,7 +3566,7 @@ dependencies = [ [[package]] name = "mithrildemo" -version = "0.1.27" +version = "0.1.28" dependencies = [ "base64 0.21.7", "blake2 0.10.6", diff --git a/demo/protocol-demo/Cargo.toml b/demo/protocol-demo/Cargo.toml index fe720a8963a..5e6dade92ba 100644 --- a/demo/protocol-demo/Cargo.toml +++ b/demo/protocol-demo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithrildemo" -version = "0.1.27" +version = "0.1.28" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true } diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index e33fd65bc64..c2e2b4cdc72 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.4.27" +version = "0.4.28" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 169af5549b1..9173d0279b1 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.2.152" +version = "0.2.153" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index fce4733ea97..734c0cc5ee1 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.103" +version = "0.2.104" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } From 201a1d45dd2d7def80891f95a3a280d3d8cee345 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 24 Jan 2024 11:34:25 +0100 Subject: [PATCH 20/25] feat: implement Merkle tree in common --- Cargo.lock | 10 + mithril-common/Cargo.toml | 1 + .../src/crypto_helper/merkle_tree.rs | 185 ++++++++++++++++++ mithril-common/src/crypto_helper/mod.rs | 1 + 4 files changed, 197 insertions(+) create mode 100644 mithril-common/src/crypto_helper/merkle_tree.rs diff --git a/Cargo.lock b/Cargo.lock index 080d25ae7bd..66bf6fd0671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -872,6 +872,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ckb-merkle-mountain-range" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15193decfa1e0b151ce19e42d118db048459a27720fb3de7d3103c30adccb12" +dependencies = [ + "cfg-if", +] + [[package]] name = "clap" version = "4.4.18" @@ -3424,6 +3433,7 @@ dependencies = [ "bech32", "blake2 0.10.6", "chrono", + "ckb-merkle-mountain-range", "criterion", "digest 0.10.7", "ed25519-dalek", diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 9173d0279b1..8e6fa70bb89 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -22,6 +22,7 @@ async-trait = "0.1.73" bech32 = "0.9.1" blake2 = "0.10.6" chrono = { version = "0.4.31", features = ["serde"] } +ckb-merkle-mountain-range = "0.6.0" digest = "0.10.7" ed25519-dalek = { version = "2.0.0", features = ["rand_core", "serde"] } fixed = "1.24.0" diff --git a/mithril-common/src/crypto_helper/merkle_tree.rs b/mithril-common/src/crypto_helper/merkle_tree.rs new file mode 100644 index 00000000000..f36e320311a --- /dev/null +++ b/mithril-common/src/crypto_helper/merkle_tree.rs @@ -0,0 +1,185 @@ +use anyhow::anyhow; +use blake2::{Blake2s256, Digest}; +use ckb_merkle_mountain_range::{util::MemStore, Merge, MerkleProof, Result as MMRResult, MMR}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, ops::Deref}; + +use crate::{StdError, StdResult}; + +/// Alias for a byte +type Bytes = Vec; + +/// Alias for a Merkle tree leaf position +type MKTreeLeafPosition = u64; + +/// A node of a Merkle tree +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct MKTreeNode { + hash: Bytes, +} + +impl MKTreeNode { + /// MKTreeNode factory + pub fn new(hash: Bytes) -> Self { + Self { hash } + } +} + +impl Deref for MKTreeNode { + type Target = Bytes; + + fn deref(&self) -> &Self::Target { + &self.hash + } +} + +impl From for MKTreeNode { + fn from(other: String) -> Self { + Self { + hash: other.as_bytes().to_vec(), + } + } +} + +impl TryFrom> for MKTreeNode { + type Error = StdError; + fn try_from(other: MKTree) -> Result { + other.compute_root() + } +} + +struct MergeMKTreeNode {} + +impl Merge for MergeMKTreeNode { + type Item = MKTreeNode; + + fn merge(lhs: &Self::Item, rhs: &Self::Item) -> MMRResult { + let mut hasher = Blake2s256::new(); + hasher.update(lhs.deref()); + hasher.update(rhs.deref()); + let hash_merge = hasher.finalize(); + + Ok(Self::Item::new(hash_merge.to_vec())) + } +} + +/// A Merkle proof +#[derive(Serialize, Deserialize)] +pub struct MKProof { + inner_root: MKTreeNode, + inner_leaves: Vec<(MKTreeLeafPosition, MKTreeNode)>, + inner_proof_size: u64, + inner_proof_items: Vec, +} + +impl MKProof { + /// Verification of a Merkle proof + pub fn verify(&self) -> StdResult<()> { + MerkleProof::::new( + self.inner_proof_size, + self.inner_proof_items.clone(), + ) + .verify(self.inner_root.to_owned(), self.inner_leaves.to_owned())? + .then_some(()) + .ok_or(anyhow!("Invalid MKProof")) + } +} + +/// A Merkle tree +pub struct MKTree<'a> { + inner_leaves: HashMap<&'a MKTreeNode, MKTreeLeafPosition>, + inner_tree: MMR>, +} + +impl<'a> MKTree<'a> { + /// MKTree factory + pub fn new(leaves: &'a [MKTreeNode], store: &'a MemStore) -> StdResult { + let mut inner_tree = + MMR::>::new(0, store); + let mut inner_leaves = HashMap::new(); + for leaf in leaves { + let inner_tree_position = inner_tree.push(leaf.to_owned())?; + inner_leaves.insert(leaf, inner_tree_position); + } + inner_tree.commit()?; + + Ok(Self { + inner_leaves, + inner_tree, + }) + } + + /// Number of leaves in the Merkle tree + pub fn total_leaves(&self) -> usize { + self.inner_leaves.len() + } + + /// Generate root of the Merkle tree + pub fn compute_root(&self) -> StdResult { + Ok(self.inner_tree.get_root()?) + } + + /// Generate Merkle proof of memberships in the tree + pub fn compute_proof(&self, leaves: &[MKTreeNode]) -> StdResult { + let inner_leaves = leaves + .iter() + .map(|leaf| { + if let Some(leaf_position) = self.inner_leaves.get(leaf) { + Ok((*leaf_position, leaf.to_owned())) + } else { + Err(anyhow!("Leaf not found in the Merkle tree")) + } + }) + .collect::>>()?; + let proof = self.inner_tree.gen_proof( + inner_leaves + .iter() + .map(|(leaf_position, _leaf)| *leaf_position) + .collect(), + )?; + return Ok(MKProof { + inner_root: self.compute_root()?, + inner_leaves, + inner_proof_size: proof.mmr_size(), + inner_proof_items: proof.proof_items().to_vec(), + }); + } +} + +#[cfg(test)] +mod tests { + use ckb_merkle_mountain_range::util::MemStore; + + use super::MKTree; + + #[test] + fn test_should_accept_valid_proof_generated_by_merkle_tree() { + let total_leaves = 100000; + let leaves = (0..total_leaves) + .map(|i| format!("test-{i}").into()) + .collect::>(); + let store = MemStore::default(); + let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail"); + let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()]; + let proof = mktree + .compute_proof(leaves_to_verify) + .expect("MKProof generation should not fail"); + proof.verify().expect("The MKProof should be valid"); + } + + #[test] + fn test_should_reject_invalid_proof_generated_by_merkle_tree() { + let total_leaves = 100000; + let leaves = (0..total_leaves) + .map(|i| format!("test-{i}").into()) + .collect::>(); + let store = MemStore::default(); + let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail"); + let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()]; + let mut proof = mktree + .compute_proof(leaves_to_verify) + .expect("MKProof generation should not fail"); + proof.inner_root = leaves[10].to_owned(); + proof.verify().expect_err("The MKProof should be invalid"); + } +} diff --git a/mithril-common/src/crypto_helper/mod.rs b/mithril-common/src/crypto_helper/mod.rs index 998b5df8523..35bafb23bd1 100644 --- a/mithril-common/src/crypto_helper/mod.rs +++ b/mithril-common/src/crypto_helper/mod.rs @@ -5,6 +5,7 @@ mod codec; mod conversions; mod era; mod genesis; +mod merkle_tree; #[cfg(feature = "test_tools")] pub mod tests_setup; mod types; From 04e95f7d97bb7c34c83cf58ac100b758250a357f Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Wed, 24 Jan 2024 15:08:06 +0100 Subject: [PATCH 21/25] fix: update README files --- mithril-client-wasm/README.md | 2 +- mithril-client-wasm/npm/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mithril-client-wasm/README.md b/mithril-client-wasm/README.md index bccd72f11d3..6351992fdea 100644 --- a/mithril-client-wasm/README.md +++ b/mithril-client-wasm/README.md @@ -1,4 +1,4 @@ -# Mithril-client-wasm ![cnpm](https://img.shields.io/npm/v/mithril-client.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE-APACHE) [![Discord](https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square)](https://discord.gg/5kaErDKDRq) +# Mithril-client-wasm ![cnpm](https://img.shields.io/npm/v/@mithril-dev/mithril-client-wasm.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE-APACHE) [![Discord](https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square)](https://discord.gg/5kaErDKDRq) **This is a work in progress** 🛠 diff --git a/mithril-client-wasm/npm/README.md b/mithril-client-wasm/npm/README.md index 7854d8306d7..cc6f92f636a 100644 --- a/mithril-client-wasm/npm/README.md +++ b/mithril-client-wasm/npm/README.md @@ -1,4 +1,4 @@ -# Mithril-client-wasm ![cnpm](https://img.shields.io/npm/v/mithril-client.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE-APACHE) [![Discord](https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square)](https://discord.gg/5kaErDKDRq) +# Mithril-client-wasm ![cnpm](https://img.shields.io/npm/v/@mithril-dev/mithril-client-wasm.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE-APACHE) [![Discord](https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square)](https://discord.gg/5kaErDKDRq) **This is a work in progress** 🛠 From 4c852def35761606df4c0413275d46a8b94aa69d Mon Sep 17 00:00:00 2001 From: Damien LACHAUME / PALO-IT Date: Wed, 24 Jan 2024 15:58:21 +0100 Subject: [PATCH 22/25] Update crate version --- Cargo.lock | 2 +- mithril-client-wasm/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 080d25ae7bd..59da5643fc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3402,7 +3402,7 @@ dependencies = [ [[package]] name = "mithril-client-wasm" -version = "0.1.7" +version = "0.1.8" dependencies = [ "async-trait", "futures", diff --git a/mithril-client-wasm/Cargo.toml b/mithril-client-wasm/Cargo.toml index c438e901d9c..2bd10d1e679 100644 --- a/mithril-client-wasm/Cargo.toml +++ b/mithril-client-wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-client-wasm" -version = "0.1.7" +version = "0.1.8" description = "Mithril client WASM" authors = { workspace = true } edition = { workspace = true } From 644be06913fc45f587f25cbf335f7dca0271fb18 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 24 Jan 2024 12:30:47 +0100 Subject: [PATCH 23/25] feat: compute protocol message for Cardano transactions --- .../src/crypto_helper/merkle_tree.rs | 67 ++++++++++-- mithril-common/src/crypto_helper/mod.rs | 1 + .../src/entities/cardano_transaction.rs | 49 +++++++++ .../signable_builder/cardano_transactions.rs | 103 +++++++++++++++++- 4 files changed, 207 insertions(+), 13 deletions(-) diff --git a/mithril-common/src/crypto_helper/merkle_tree.rs b/mithril-common/src/crypto_helper/merkle_tree.rs index f36e320311a..fcc29832042 100644 --- a/mithril-common/src/crypto_helper/merkle_tree.rs +++ b/mithril-common/src/crypto_helper/merkle_tree.rs @@ -23,6 +23,17 @@ impl MKTreeNode { pub fn new(hash: Bytes) -> Self { Self { hash } } + + /// Create a MKTreeNode from a hex representation + pub fn from_hex(hex: &str) -> StdResult { + let hash = hex::decode(hex)?; + Ok(Self { hash }) + } + + /// Create a hex representation of the MKTreeNode + pub fn to_hex(&self) -> String { + hex::encode(&self.hash) + } } impl Deref for MKTreeNode { @@ -35,6 +46,14 @@ impl Deref for MKTreeNode { impl From for MKTreeNode { fn from(other: String) -> Self { + Self { + hash: other.as_str().into(), + } + } +} + +impl From<&str> for MKTreeNode { + fn from(other: &str) -> Self { Self { hash: other.as_bytes().to_vec(), } @@ -48,6 +67,12 @@ impl TryFrom> for MKTreeNode { } } +impl ToString for MKTreeNode { + fn to_string(&self) -> String { + String::from_utf8_lossy(&self.hash).to_string() + } +} + struct MergeMKTreeNode {} impl Merge for MergeMKTreeNode { @@ -85,17 +110,19 @@ impl MKProof { } } +/// A Merkle tree store +pub type MKTreeStore = MemStore; + /// A Merkle tree pub struct MKTree<'a> { inner_leaves: HashMap<&'a MKTreeNode, MKTreeLeafPosition>, - inner_tree: MMR>, + inner_tree: MMR, } impl<'a> MKTree<'a> { /// MKTree factory - pub fn new(leaves: &'a [MKTreeNode], store: &'a MemStore) -> StdResult { - let mut inner_tree = - MMR::>::new(0, store); + pub fn new(leaves: &'a [MKTreeNode], store: &'a MKTreeStore) -> StdResult { + let mut inner_tree = MMR::::new(0, store); let mut inner_leaves = HashMap::new(); for leaf in leaves { let inner_tree_position = inner_tree.push(leaf.to_owned())?; @@ -148,9 +175,22 @@ impl<'a> MKTree<'a> { #[cfg(test)] mod tests { - use ckb_merkle_mountain_range::util::MemStore; + use super::*; - use super::MKTree; + #[test] + fn test_golden_merkle_root() { + let leaves = vec!["golden-1", "golden-2", "golden-3", "golden-4", "golden-5"]; + let leaves: Vec = leaves.into_iter().map(|l| l.into()).collect(); + let store = MKTreeStore::default(); + let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail"); + let mkroot = mktree + .compute_root() + .expect("MKRoot generation should not fail"); + assert_eq!( + "3bbced153528697ecde7345a22e50115306478353619411523e804f2323fd921", + mkroot.to_hex() + ); + } #[test] fn test_should_accept_valid_proof_generated_by_merkle_tree() { @@ -158,7 +198,7 @@ mod tests { let leaves = (0..total_leaves) .map(|i| format!("test-{i}").into()) .collect::>(); - let store = MemStore::default(); + let store = MKTreeStore::default(); let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail"); let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()]; let proof = mktree @@ -173,7 +213,7 @@ mod tests { let leaves = (0..total_leaves) .map(|i| format!("test-{i}").into()) .collect::>(); - let store = MemStore::default(); + let store = MKTreeStore::default(); let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail"); let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()]; let mut proof = mktree @@ -182,4 +222,15 @@ mod tests { proof.inner_root = leaves[10].to_owned(); proof.verify().expect_err("The MKProof should be invalid"); } + + #[test] + fn tree_node_from_to_string() { + let expected_str = "my_string"; + let expected_string = expected_str.to_string(); + let node_str: MKTreeNode = expected_str.into(); + let node_string: MKTreeNode = expected_string.clone().into(); + + assert_eq!(node_str.to_string(), expected_str); + assert_eq!(node_string.to_string(), expected_string); + } } diff --git a/mithril-common/src/crypto_helper/mod.rs b/mithril-common/src/crypto_helper/mod.rs index 35bafb23bd1..de9b72086b9 100644 --- a/mithril-common/src/crypto_helper/mod.rs +++ b/mithril-common/src/crypto_helper/mod.rs @@ -23,6 +23,7 @@ pub use era::{ EraMarkersVerifierSignature, EraMarkersVerifierVerificationKey, }; pub use genesis::{ProtocolGenesisError, ProtocolGenesisSigner, ProtocolGenesisVerifier}; +pub use merkle_tree::{MKProof, MKTree, MKTreeNode, MKTreeStore}; pub use types::*; /// The current protocol version diff --git a/mithril-common/src/entities/cardano_transaction.rs b/mithril-common/src/entities/cardano_transaction.rs index 066c413d5d3..dad91cd1309 100644 --- a/mithril-common/src/entities/cardano_transaction.rs +++ b/mithril-common/src/entities/cardano_transaction.rs @@ -1,3 +1,5 @@ +use crate::crypto_helper::MKTreeNode; + use super::ImmutableFileNumber; /// TransactionHash is the unique identifier of a cardano transaction. @@ -18,3 +20,50 @@ pub struct CardanoTransaction { /// Immutable file number of the transaction pub immutable_file_number: ImmutableFileNumber, } + +impl CardanoTransaction { + /// CardanoTransaction factory + pub fn new( + hash: &str, + block_number: BlockNumber, + immutable_file_number: ImmutableFileNumber, + ) -> Self { + Self { + transaction_hash: hash.to_owned(), + block_number, + immutable_file_number, + } + } +} + +impl From for MKTreeNode { + fn from(other: CardanoTransaction) -> Self { + (&other).into() + } +} + +impl From<&CardanoTransaction> for MKTreeNode { + fn from(other: &CardanoTransaction) -> Self { + MKTreeNode::new(other.transaction_hash.as_bytes().to_vec()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_convert_cardano_transaction_to_merkle_tree_node() { + let transaction = CardanoTransaction { + transaction_hash: "tx-hash-123".to_string(), + block_number: 1, + immutable_file_number: 1, + }; + let computed_mktree_node: MKTreeNode = transaction.into(); + let expected_mk_tree_node = MKTreeNode::new("tx-hash-123".as_bytes().to_vec()); + let non_expected_mk_tree_node = MKTreeNode::new("tx-hash-456".as_bytes().to_vec()); + + assert_eq!(expected_mk_tree_node, computed_mktree_node); + assert_ne!(non_expected_mk_tree_node, computed_mktree_node); + } +} diff --git a/mithril-common/src/signable_builder/cardano_transactions.rs b/mithril-common/src/signable_builder/cardano_transactions.rs index 5fdaea7111f..35d2655281e 100644 --- a/mithril-common/src/signable_builder/cardano_transactions.rs +++ b/mithril-common/src/signable_builder/cardano_transactions.rs @@ -3,11 +3,13 @@ use std::{ sync::Arc, }; +use anyhow::Context; use async_trait::async_trait; use slog::{debug, Logger}; use crate::{ cardano_transaction_parser::TransactionParser, + crypto_helper::{MKTree, MKTreeNode, MKTreeStore}, entities::{Beacon, CardanoTransaction, ProtocolMessage, ProtocolMessagePartKey}, signable_builder::SignableBuilder, StdResult, @@ -47,6 +49,18 @@ impl CardanoTransactionsSignableBuilder { dirpath: dirpath.to_owned(), } } + + fn compute_merkle_root(&self, transactions: &[CardanoTransaction]) -> StdResult { + let store = MKTreeStore::default(); + let leaves = transactions.iter().map(|tx| tx.into()).collect::>(); + let mk_tree = MKTree::new(&leaves, &store) + .with_context(|| "CardanoTransactionsSignableBuilder failed to compute MKTree")?; + let mk_root = mk_tree + .compute_root() + .with_context(|| "CardanoTransactionsSignableBuilder failed to compute MKTree root")?; + + Ok(mk_root) + } } #[async_trait] @@ -75,10 +89,12 @@ impl SignableBuilder for CardanoTransactionsSignableBuilder { .await?; } + let mk_root = self.compute_merkle_root(&transactions)?; + let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part( ProtocolMessagePartKey::CardanoTransactionsMerkleRoot, - format!("{beacon}-{}", transactions.len()), + mk_root.to_hex(), ); Ok(protocol_message) @@ -100,18 +116,95 @@ mod tests { slog::Logger::root(Arc::new(drain), slog::o!()) } + #[tokio::test] + async fn test_compute_merkle_root() { + let transaction_1 = CardanoTransaction::new("tx-hash-123", 1, 1); + let transaction_2 = CardanoTransaction::new("tx-hash-456", 2, 1); + let transaction_3 = CardanoTransaction::new("tx-hash-789", 3, 1); + let transaction_4 = CardanoTransaction::new("tx-hash-abc", 4, 1); + + let transactions_set_reference = vec![ + transaction_1.clone(), + transaction_2.clone(), + transaction_3.clone(), + ]; + let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new( + Arc::new(DumbTransactionParser::new( + transactions_set_reference.clone(), + )), + Arc::new(MockTransactionStore::new()), + Path::new("/tmp"), + create_logger(), + ); + + let merkle_root_reference = cardano_transaction_signable_builder + .compute_merkle_root(&transactions_set_reference) + .unwrap(); + { + let transactions_set = vec![transaction_1.clone()]; + let mk_root = cardano_transaction_signable_builder + .compute_merkle_root(&transactions_set) + .unwrap(); + assert_ne!(merkle_root_reference, mk_root); + } + { + let transactions_set = vec![transaction_1.clone(), transaction_2.clone()]; + let mk_root = cardano_transaction_signable_builder + .compute_merkle_root(&transactions_set) + .unwrap(); + assert_ne!(merkle_root_reference, mk_root); + } + { + let transactions_set = vec![ + transaction_1.clone(), + transaction_2.clone(), + transaction_3.clone(), + transaction_4.clone(), + ]; + let mk_root = cardano_transaction_signable_builder + .compute_merkle_root(&transactions_set) + .unwrap(); + assert_ne!(merkle_root_reference, mk_root); + } + + { + // Transactions in a different order returns a different merkle root. + let transactions_set = vec![ + transaction_1.clone(), + transaction_3.clone(), + transaction_2.clone(), + ]; + let mk_root = cardano_transaction_signable_builder + .compute_merkle_root(&transactions_set) + .unwrap(); + assert_ne!(merkle_root_reference, mk_root); + } + } + #[tokio::test] async fn test_compute_signable() { let beacon = Beacon::default(); - let transactions_count = 0; - let transaction_parser = Arc::new(DumbTransactionParser::new(vec![])); - let transaction_store = Arc::new(MockTransactionStore::new()); + let transactions = vec![ + CardanoTransaction::new("tx-hash-123", 1, 1), + CardanoTransaction::new("tx-hash-456", 2, 1), + CardanoTransaction::new("tx-hash-789", 3, 1), + ]; + let transaction_parser = Arc::new(DumbTransactionParser::new(transactions.clone())); + let mut mock_transaction_store = MockTransactionStore::new(); + mock_transaction_store + .expect_store_transactions() + .times(1) + .returning(|_| Ok(())); + let transaction_store = Arc::new(mock_transaction_store); let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::new( transaction_parser, transaction_store, Path::new("/tmp"), create_logger(), ); + let mk_root = cardano_transactions_signable_builder + .compute_merkle_root(&transactions) + .unwrap(); let signable = cardano_transactions_signable_builder .compute_protocol_message(beacon.clone()) .await @@ -119,7 +212,7 @@ mod tests { let mut signable_expected = ProtocolMessage::new(); signable_expected.set_message_part( ProtocolMessagePartKey::CardanoTransactionsMerkleRoot, - format!("{beacon}-{transactions_count}"), + mk_root.to_hex(), ); assert_eq!(signable_expected, signable); } From d1c973724d9c4c995a64e8787c3a04e290cdff57 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 24 Jan 2024 16:20:59 +0100 Subject: [PATCH 24/25] fix: collisions on artifact ids for Cardano transactions --- .../artifact_builder/cardano_transactions.rs | 17 ++++++++++++----- .../src/services/signed_entity.rs | 3 ++- .../entities/cardano_transactions_commitment.rs | 17 +++++++++++++---- mithril-common/src/entities/signed_entity.rs | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/mithril-aggregator/src/artifact_builder/cardano_transactions.rs b/mithril-aggregator/src/artifact_builder/cardano_transactions.rs index 1bf22a4209e..19c265bd43b 100644 --- a/mithril-aggregator/src/artifact_builder/cardano_transactions.rs +++ b/mithril-aggregator/src/artifact_builder/cardano_transactions.rs @@ -36,17 +36,23 @@ impl ArtifactBuilder for CardanoTransacti .with_context(|| { format!( "Can not compute CardanoTransactionsCommitment artifact for signed_entity: {:?}", - SignedEntityType::CardanoTransactions(beacon) + SignedEntityType::CardanoTransactions(beacon.clone()) ) })?; - Ok(CardanoTransactionsCommitment::new(merkle_root.to_string())) + Ok(CardanoTransactionsCommitment::new( + merkle_root.to_string(), + beacon, + )) } } #[cfg(test)] mod tests { - use mithril_common::{entities::ProtocolMessage, test_utils::fake_data}; + use mithril_common::{ + entities::ProtocolMessage, + test_utils::fake_data::{self}, + }; use super::*; @@ -65,10 +71,11 @@ mod tests { let cardano_transaction_artifact_builder = CardanoTransactionsArtifactBuilder::new(); let artifact = cardano_transaction_artifact_builder - .compute_artifact(Beacon::default(), &certificate) + .compute_artifact(certificate.beacon.clone(), &certificate) .await .unwrap(); - let artifact_expected = CardanoTransactionsCommitment::new("merkleroot".to_string()); + let artifact_expected = + CardanoTransactionsCommitment::new("merkleroot".to_string(), certificate.beacon); assert_eq!(artifact_expected, artifact); } diff --git a/mithril-aggregator/src/services/signed_entity.rs b/mithril-aggregator/src/services/signed_entity.rs index eac53999471..3e18ad15298 100644 --- a/mithril-aggregator/src/services/signed_entity.rs +++ b/mithril-aggregator/src/services/signed_entity.rs @@ -405,7 +405,8 @@ mod tests { #[tokio::test] async fn build_artifact_for_cardano_transactions_store_nothing_in_db() { - let expected = CardanoTransactionsCommitment::new("merkle_root".to_string()); + let expected = + CardanoTransactionsCommitment::new("merkle_root".to_string(), Beacon::default()); let mut mock_signed_entity_storer = MockSignedEntityStorer::new(); mock_signed_entity_storer .expect_store_signed_entity() diff --git a/mithril-common/src/entities/cardano_transactions_commitment.rs b/mithril-common/src/entities/cardano_transactions_commitment.rs index d90e3da20af..dab686eb0dd 100644 --- a/mithril-common/src/entities/cardano_transactions_commitment.rs +++ b/mithril-common/src/entities/cardano_transactions_commitment.rs @@ -1,23 +1,32 @@ +use crate::signable_builder::Artifact; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; -use crate::signable_builder::Artifact; +use super::Beacon; /// Commitment of a set of Cardano transactions #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct CardanoTransactionsCommitment { merkle_root: String, + beacon: Beacon, } impl CardanoTransactionsCommitment { /// Creates a new [CardanoTransactionsCommitment] - pub fn new(merkle_root: String) -> Self { - Self { merkle_root } + pub fn new(merkle_root: String, beacon: Beacon) -> Self { + Self { + merkle_root, + beacon, + } } } #[typetag::serde] impl Artifact for CardanoTransactionsCommitment { fn get_id(&self) -> String { - self.merkle_root.clone() + let mut hasher = Sha256::new(); + hasher.update(self.merkle_root.clone().as_bytes()); + hasher.update(self.beacon.compute_hash().as_bytes()); + hex::encode(hasher.finalize()) } } diff --git a/mithril-common/src/entities/signed_entity.rs b/mithril-common/src/entities/signed_entity.rs index 6bd1b069b46..c8a20a6f89d 100644 --- a/mithril-common/src/entities/signed_entity.rs +++ b/mithril-common/src/entities/signed_entity.rs @@ -70,7 +70,7 @@ impl SignedEntity { signed_entity_id: "snapshot-id-123".to_string(), signed_entity_type: SignedEntityType::CardanoTransactions(Beacon::default()), certificate_id: "certificate-hash-123".to_string(), - artifact: CardanoTransactionsCommitment::new("mkroot123".to_string()), + artifact: CardanoTransactionsCommitment::new("mkroot123".to_string(), Beacon::default()), created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z") .unwrap() .with_timezone(&Utc), From 50c9bc7d3384e24bef9d04136ee6610813bbe26e Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 25 Jan 2024 12:03:06 +0100 Subject: [PATCH 25/25] chore: update crates versions --- Cargo.lock | 4 ++-- mithril-aggregator/Cargo.toml | 2 +- mithril-common/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66bf6fd0671..2cccee7e789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3288,7 +3288,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.4.28" +version = "0.4.29" dependencies = [ "anyhow", "async-trait", @@ -3426,7 +3426,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.2.153" +version = "0.2.154" dependencies = [ "anyhow", "async-trait", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index c2e2b4cdc72..cb0789aad78 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.4.28" +version = "0.4.29" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 8e6fa70bb89..ea2c1754309 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.2.153" +version = "0.2.154" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true }