diff --git a/.env.example b/.env.example index 2335ae7..2097017 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ -DATABASE_URL=postgres://mina:whatever@localhost:5432/archive +MINAMESH_ARCHIVE_DATABASE_URL=postgres://mina:whatever@localhost:5432/archive +MINAMESH_GENESIS_BLOCK_IDENTIFIER_STATE_HASH=3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP +MINAMESH_GENESIS_BLOCK_IDENTIFIER_HEIGHT=359605 + RUST_LOG=debug,error,mina_mesh=info RUST_ENV=production diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 731d913..4a5dc91 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -56,6 +56,7 @@ jobs: key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} - name: Setup run: | + cp .env.example .env just get-mainnet-archive-db just pg just wait-for-pg diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index a1209aa..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=${workspaceFolderBasename}" - ], - "filter": { - "name": "${workspaceFolderBasename}", - "kind": "lib" - } - }, - "args": ["--nocapture"], - "cwd": "${workspaceFolder}", - "env": { - "RUST_LOG": "debug" - } - } - ] -} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..872c154 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,117 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a +harassment-free experience for everyone, regardless of age, body size, visible or invisible +disability, ethnicity, sex characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or +sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and +healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the + experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their + explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior +and will take appropriate and fair corrective action in response to any behavior that they deem +inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, +code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and +will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is +officially representing the community in public spaces. Examples of representing our community +include using an official email address, posting via an official social media account, or acting as +an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community +leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and +investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any +incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for +any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or +unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the +nature of the violation and an explanation of why the behavior was inappropriate. A public apology +may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people +involved, including unsolicited interaction with those enforcing the Code of Conduct, for a +specified period of time. This includes avoiding interactions in community spaces as well as +external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate +behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the +community for a specified period of time. No public or private interaction with the people involved, +including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this +period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including +sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement +of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d84cbfa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contributing + +When contributing to this repository, please check our open issues and whether there is already an +issue related to your idea. Please first discuss the change you wish to make in a GitHub issue and +wait for a reply from the maintainers of this repository before making a change. + +We have a [code of conduct](CODE_OF_CONDUCT.md); please follow it in all your interactions relating +to the project. + +## Environment setup + +To develop on your machine, install the following (and please submit issues if errors crop up) + +- [Rust](https://www.rust-lang.org/tools/install) +- [Docker](https://docs.docker.com/get-docker/) +- [dprint](https://dprint.dev/) +- [Just](https://github.com/casey/just) +- [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) +- [insta](https://insta.rs/) + +## Pull requests + +**For a pull request to be merged it must at least:** + +:white_check_mark:   Pass CI + +:white_check_mark:   Have one approving review + +:white_check_mark:   Have the PR title follow +[conventional commit](https://www.conventionalcommits.org/) + +**Ideally, a good pull request should:** + +:clock3:   Take less than 15 minutes to review + +:open_book:   Have a meaningful description (describes the problem being solved) + +:one:   Introduce one feature or solve one bug at a time, for which an open issue already +exists. In case of a project wide refactoring, a larger PR is to be expected, but the reviewer +should be more carefully guided through it + +:jigsaw:   Issues that seem too big for a PR that can be reviewed in 15 minutes or PRs that +need to touch other issues should be discussed and probably split differently before starting any +development + +:dart:   Handle renaming, moving files, linting and formatting separately (not alongside +features or bug fixes) + +:test_tube:   Add tests for new functionality + +**Draft pull requests for early feedback are welcome and do not need to adhere to any guidelines.** + +When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews +should finish with approval unless there are issues that would result in: + +:x:   Buggy behavior + +:x:   Undue maintenance burden + +:x:   Measurable performance issues + +:x:   Feature reduction (i.e. it removes some aspect of functionality that a significant +minority of users rely on) + +:x:   Uselessness (i.e. it does not strictly add a feature or fix a known issue) + +:x:   Disabling a compiler feature to introduce code that wouldn't compile + +## Releases + +Declaring formal releases remains the prerogative of the project maintainer(s). + +## License + +By contributing to project, you agree that your contributions will be licensed under its +[Apache license](LICENSE). + +## Changes to this arrangement + +This is an experiment and feedback is welcome! This document may also be subject to pull-requests or +changes by contributors where you believe you have something valuable to add or change. + +## Testing + +### Ensure Test DB Accessible + +- **Stop PostgreSQL**: To stop the PostgreSQL instance: + + ```bash + just pg-down + ``` + +- **Restart PostgreSQL**: To restart without reinitializing the database (useful if the database is + already set up): + + ```bash + just pg-up + ``` + +> You only need to reinitialize the database if you want the latest data dump. + +### Run Tests + +```bash +cargo test +``` + +### Update Snapshots + +``` +cargo insta review +``` diff --git a/README.md b/README.md index 9b59b58..86f0dd6 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,59 @@ # Mina Mesh -[![checks](https://github.com/MinaFoundation/MinaMesh/actions/workflows/checks.yaml/badge.svg)](https://github.com/MinaFoundation/MinaMesh/actions/workflows/checks.yaml) - -## Overview - Mina Mesh is an implementation of the [Coinbase Mesh specification](https://docs.cdp.coinbase.com/mesh/docs/welcome) for the [Mina blockchain](https://minaprotocol.com/). -## Building - -To build the project: - -```bash -cargo build -``` - -The binary will be available at: - -```bash -target/debug/mina_mesh -``` - -## Running - -Mina Mesh requires access to a PostgreSQL Archive database and a Mina GraphQL endpoint. By default, -the configuration points to the mainnet, making it easy to get started. You can override the -configuration by either passing arguments or setting environment variables via a `.env` file (an -example is provided as `.env.example`). - -### Quick Start with Mainnet - -1. **Set up the PostgreSQL Archive Database** - -Use the predefined `just` commands to set up and start the PostgreSQL database: +> Note: Mina Mesh is WIP and should not yet be used in production. -```bash -just setup-archive-db -``` - -> Note: This process sets up the PostgreSQL docker using the latest mainnet archive database. +## Installation -2. **Run the Mina Mesh Server** +Ensure you have the Rust toolchain installed. If you do not, see +[installation instructions here](https://www.rust-lang.org/tools/install). -To start the server with default settings (mainnet configuration): +```sh +# Install the mina-mesh executable +cargo install mina-mesh -```bash -target/debug/mina_mesh serve +# Confirm the installation was successful +mina-mesh --help ``` -The server will listen on `0.0.0.0:3000` by default. +## Environment -### Playground Mode +The server depends on several environment variables. -You can enable a playground mode, which provides a simplified testing interface, by adding the -`--playground` flag: - -```bash -cargo run -- serve --playground -``` +- `MINAMESH_PROXY_URL`: a Mina proxy (GraphQL) endpoint. The default is + `https://mainnet.minaprotocol.network/graphql`. +- `MINAMESH_ARCHIVE_DATABASE_URL`: a connection string referencing a Mina archive database. +- `MINAMESH_GENESIS_BLOCK_IDENTIFIER_HEIGHT` and `MINAMESH_GENESIS_BLOCK_IDENTIFIER_STATE_HASH`: we + can retrieve these using the `fetch-genesis-block-identifier` command. -When enabled, you can access the playground at the root URL (`/`). - -### Configuration + ```sh + mina-mesh fetch-genesis-block-identifier >> .env + ``` -Mina Mesh can be configured through command-line options or by using environment variables. For -convenience, you can use a `.env` file. To get started, copy the provided `.env.example`: +## Instantiate the Server -```bash -cp .env.example .env +```sh +mina-mesh serve --playground ``` -Then modify the `.env` file to suit your environment. The available configurations include: +> Note: the presence of the `--playground` flag enables the serving of an OpenAPI playground in +> response to `GET /`. To disable this endpoint, omit the `--playground` flag. -- **Mina GraphQL Endpoint**: `MINA_PROXY_URL` (default: - `https://mainnet.minaprotocol.network/graphql`) -- **PostgreSQL Archive Database URL**: `MINA_ARCHIVE_DATABASE_URL` (default: - `postgres://mina:whatever@localhost:5432/archive`) -- **Genesis Block Identifier**: `MINA_GENESIS_BLOCK_IDENTIFIER_HEIGHT`, - `MINA_GENESIS_BLOCK_IDENTIFIER_STATE_HASH` +Visit [`http://0.0.0.0:3000`](http://0.0.0.0:3000) for an interactive playground with which you can +explore and test endpoints. -> You can also pass these options as arguments to `mina_mesh serve` to override the defaults. +## Code of Conduct -## Testing +Everyone interacting in this repo is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). -Running the tests requires having Archive database available [see: -[Quick Start with Mainnet](#quick-start-with-mainnet)]. Once the setup is complete you can run tests -using: +## Contributing -```bash -just test -``` +Contributions are welcome and appreciated! Check out the [contributing guide](CONTRIBUTING.md) +before you dive in. -### Managing PostgreSQL - -- **Stop PostgreSQL**: To stop the PostgreSQL instance: - - ```bash - just pg-down - ``` - -- **Restart PostgreSQL**: To restart without reinitializing the database (useful if the database is - already set up): - - ```bash - just pg-up - ``` +## License -> You only need to reinitialize the database if you want the latest data dump. +Mina Mesh is [Apache licensed](LICENSE). diff --git a/src/commands/fetch_genesis_block_identifier.rs b/src/commands/fetch_genesis_block_identifier.rs index 11b1bf2..f2e13d0 100644 --- a/src/commands/fetch_genesis_block_identifier.rs +++ b/src/commands/fetch_genesis_block_identifier.rs @@ -2,27 +2,24 @@ use anyhow::{bail, Result}; use clap::Args; use cynic::{http::ReqwestExt, QueryBuilder}; -use crate::graphql::QueryGenesisBlockIdentifier; +use crate::{graphql::QueryGenesisBlockIdentifier, util::default_mina_proxy_url}; #[derive(Debug, Args)] #[command(about = "Retrieve the genesis block identifier via a proxy node GraphQL endpoint.")] pub struct FetchGenesisBlockIdentifierCommand { - #[arg(long, short = 'n', default_value = "https://mainnet.minaprotocol.network/graphql")] - proxy_node_graphql_endpoint: String, + #[arg(long, env = "MINAMESH_PROXY_URL", default_value_t = default_mina_proxy_url())] + proxy_url: String, } impl FetchGenesisBlockIdentifierCommand { pub async fn run(&self) -> Result<()> { let client = reqwest::Client::new(); - let result = client - .post(self.proxy_node_graphql_endpoint.to_owned()) - .run_graphql(QueryGenesisBlockIdentifier::build(())) - .await?; + let result = client.post(self.proxy_url.to_owned()).run_graphql(QueryGenesisBlockIdentifier::build(())).await?; if let Some(inner) = result.data { let genesis_block_hash = inner.genesis_block.state_hash.0; let genesis_block_index = inner.genesis_block.protocol_state.consensus_state.block_height.0; - println!("MINAMESH_GENESIS_BLOCK_HASH = {}", genesis_block_hash); - println!("MINAMESH_GENESIS_BLOCK_INDEX = {}", genesis_block_index); + println!("MINAMESH_GENESIS_BLOCK_IDENTIFIER_STATE_HASH = {genesis_block_hash}"); + println!("MINAMESH_GENESIS_BLOCK_IDENTIFIER_HEIGHT = {genesis_block_index}"); } else { bail!("No genesis block identifier found in the response"); } diff --git a/src/config.rs b/src/config.rs index 2a7de9e..0aac911 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,23 +1,34 @@ use anyhow::Result; -use clap::Args; +use clap::{Args, Parser}; use mesh::models::BlockIdentifier; use sqlx::PgPool; -use crate::{graphql::GraphQLClient, MinaMesh}; +use crate::{graphql::GraphQLClient, util::default_mina_proxy_url, MinaMesh}; #[derive(Debug, Args)] pub struct MinaMeshConfig { - #[arg(long, env = "MINA_PROXY_URL", default_value_t = mina_proxy_url())] + #[arg(long, env = "MINAMESH_PROXY_URL", default_value_t = default_mina_proxy_url())] pub proxy_url: String, - #[arg(long, env = "MINA_ARCHIVE_DATABASE_URL", default_value_t = database_url())] + #[arg(long, env = "MINAMESH_ARCHIVE_DATABASE_URL")] pub archive_database_url: String, - #[arg(long, env = "MINA_GENESIS_BLOCK_IDENTIFIER_HEIGHT", default_value_t = genesis_block_identifier_height())] + #[arg(long, env = "MINAMESH_GENESIS_BLOCK_IDENTIFIER_HEIGHT")] pub genesis_block_identifier_height: i64, - #[arg(long, env = "MINA_GENESIS_BLOCK_IDENTIFIER_STATE_HASH", default_value_t = genesis_block_identifier_state_hash())] + #[arg(long, env = "MINAMESH_GENESIS_BLOCK_IDENTIFIER_STATE_HASH")] pub genesis_block_identifier_state_hash: String, } impl MinaMeshConfig { + pub fn from_env() -> Self { + dotenv::dotenv().ok(); + return MinaMeshConfigParser::parse().config; + + #[derive(Parser)] + struct MinaMeshConfigParser { + #[command(flatten)] + config: MinaMeshConfig, + } + } + pub async fn to_mina_mesh(self) -> Result { Ok(MinaMesh { graphql_client: GraphQLClient::new(self.proxy_url.to_owned()), @@ -29,30 +40,3 @@ impl MinaMeshConfig { }) } } - -impl Default for MinaMeshConfig { - fn default() -> Self { - Self { - proxy_url: mina_proxy_url(), - archive_database_url: database_url(), - genesis_block_identifier_height: genesis_block_identifier_height(), - genesis_block_identifier_state_hash: genesis_block_identifier_state_hash(), - } - } -} - -fn mina_proxy_url() -> String { - "https://mainnet.minaprotocol.network/graphql".to_string() -} - -fn database_url() -> String { - "postgres://mina:whatever@localhost:5432/archive".to_string() -} - -fn genesis_block_identifier_height() -> i64 { - 359605 -} - -fn genesis_block_identifier_state_hash() -> String { - "3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP".to_string() -} diff --git a/src/util.rs b/src/util.rs index 27a233b..3b001cd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -47,3 +47,7 @@ impl ToString for Wrapper<&PartialBlockIdentifier> { } } } + +pub fn default_mina_proxy_url() -> String { + "https://mainnet.minaprotocol.network/graphql".to_string() +} diff --git a/tests/account_balance.rs b/tests/account_balance.rs index 687da1e..f475c23 100644 --- a/tests/account_balance.rs +++ b/tests/account_balance.rs @@ -8,7 +8,7 @@ use mina_mesh::{ #[tokio::test] async fn responses() -> Result<()> { - let mina_mesh = MinaMeshConfig::default().to_mina_mesh().await?; + let mina_mesh = MinaMeshConfig::from_env().to_mina_mesh().await?; let futures: Vec<_> = [ // cspell:disable "B62qmjJeM4Fd4FVghfhgwoE1fkEexK2Rre8WYKMnbxVwB5vtKUwvgMv", diff --git a/tests/block.rs b/tests/block.rs index c154a2e..2d309e4 100644 --- a/tests/block.rs +++ b/tests/block.rs @@ -6,7 +6,7 @@ use mina_mesh::{BlockMetadata, MinaMeshConfig, PartialBlockIdentifier}; #[tokio::test] async fn specified() -> Result<()> { - let mina_mesh = MinaMeshConfig::default().to_mina_mesh().await?; + let mina_mesh = MinaMeshConfig::from_env().to_mina_mesh().await?; let mut metadata_futures = specified_identifiers().iter().map(|item| mina_mesh.block_metadata(item)).collect::>(); let mut maybe_prev: Option> = None; @@ -38,8 +38,11 @@ fn specified_identifiers() -> &'static [PartialBlockIdentifier; 3] { #[tokio::test] async fn unspecified() -> Result<()> { - let mina_mesh = MinaMeshConfig::default().to_mina_mesh().await?; - let result = mina_mesh.block_metadata(&PartialBlockIdentifier { hash: None, index: None }).await; - assert!(result.is_ok()); + let response = MinaMeshConfig::from_env() + .to_mina_mesh() + .await? + .block_metadata(&PartialBlockIdentifier { hash: None, index: None }) + .await; + assert!(response.is_ok()); Ok(()) } diff --git a/tests/network_list.rs b/tests/network_list.rs index 63d5712..fac082d 100644 --- a/tests/network_list.rs +++ b/tests/network_list.rs @@ -4,15 +4,7 @@ use mina_mesh::MinaMeshConfig; #[tokio::test] async fn mainnet_test() -> Result<()> { - // Create a MinaMesh instance using the default configuration - let mina_mesh = MinaMeshConfig::default().to_mina_mesh().await?; - - // Call the network_list function - let result = mina_mesh.network_list().await?; - - assert!(!result.network_identifiers.is_empty()); - - let network_identifier = &result.network_identifiers[0]; - assert_debug_snapshot!(network_identifier); + let response = MinaMeshConfig::from_env().to_mina_mesh().await?.network_list().await?; + assert_debug_snapshot!(&response.network_identifiers); Ok(()) } diff --git a/tests/snapshots/network_list__mainnet_test.snap b/tests/snapshots/network_list__mainnet_test.snap index c73de61..2807195 100644 --- a/tests/snapshots/network_list__mainnet_test.snap +++ b/tests/snapshots/network_list__mainnet_test.snap @@ -1,9 +1,11 @@ --- source: tests/network_list.rs -expression: network_identifier +expression: "&response.network_identifiers" --- -NetworkIdentifier { - blockchain: "mina", - network: "mainnet", - sub_network_identifier: None, -} +[ + NetworkIdentifier { + blockchain: "mina", + network: "mainnet", + sub_network_identifier: None, + }, +] diff --git a/words.txt b/words.txt index 87f4cf8..3f75934 100644 --- a/words.txt +++ b/words.txt @@ -1,3 +1,5 @@ +MINAMESH +RUSTFLAGS codegen coinbases darklight @@ -17,11 +19,11 @@ insta isready joaosreis johnmarcou +katyo keypair lookback microschemas mina -MINAMESH navroot nocapture openapi @@ -29,7 +31,6 @@ preprocess querygen reqwest retriable -RUSTFLAGS rustfmt schnorr secp