diff --git a/Cargo.lock b/Cargo.lock index 4dd4713dd30..d02758df4a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8142,9 +8142,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50302b77192891256d180ff2551dc0c3bc4144958b49e9a16c50a0dc218958ba" +checksum = "4cf1d63d29e467457781bc08763c0348ab86c7a14713df7af5e6cc3ba88632c3" dependencies = [ "anyhow", "once_cell", @@ -8177,9 +8177,9 @@ dependencies = [ [[package]] name = "zksync_consensus_bft" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2325c7486a8280db1c26c10020350bead6eecb3de03f8bbfd878060f000cdce7" +checksum = "5e8b6a76b90b909d9fec814ff980e03ea6179139821bd6edc3df3e1b08d8e765" dependencies = [ "anyhow", "async-trait", @@ -8199,9 +8199,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5cb8ed0d59593f6147085b77142628e459ba673aa4d48fce064d5b96e31eb36" +checksum = "037bdb8bf5543676306b2faaa6dc60197a3f9335bbd1af1765d0eae4312ed427" dependencies = [ "anyhow", "blst", @@ -8223,11 +8223,12 @@ dependencies = [ [[package]] name = "zksync_consensus_executor" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247b70ec255781b3b740acb744236e771a192922ffbaa52c462b84c4ea67609f" +checksum = "5fe50d7722e70f0ab5ca5af55a749b3c7f1207ce6e0320239699ff3384593690" dependencies = [ "anyhow", + "async-trait", "rand 0.8.5", "tracing", "vise", @@ -8243,9 +8244,9 @@ dependencies = [ [[package]] name = "zksync_consensus_network" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f10626b79885a9b096cd19ee83d85ef9b0554f061a9db6946f2b7c9d1b2f49ea" +checksum = "6f89466c9523fcd1146d93a0d0b10d46b0388466c42bf4f18ca99a22ddcbe04b" dependencies = [ "anyhow", "async-trait", @@ -8278,9 +8279,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffe3e47d99eb943eb94f2f5c9d929b1192bf3e8d1434de0fa6f0090f9c1197e" +checksum = "5ade360294fd4b8191adb24b034c9dd87742c70d1859a2a405d24f1d1fb5ecaf" dependencies = [ "anyhow", "bit-vec", @@ -8300,9 +8301,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae9a0ec64ce9c0af346e50cc87dc257c30259101ce9675b408cb883e096087" +checksum = "632a5a54e7359ff29ca34185859c766dab2d531e01f583d0b1bff343249254ef" dependencies = [ "anyhow", "async-trait", @@ -8320,9 +8321,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24dc6135abeefa80f617eb2903fe43d137d362bf673f0651b4894b17069d1fb1" +checksum = "d12966b4cfa166abbc1d702640cefea59fa15f8509e2c959f06e2cfde97ef429" dependencies = [ "anyhow", "rand 0.8.5", @@ -9260,9 +9261,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e7c7820f290db565a1b4ff73aa1175cd7d31498fca8d859eb5aceebd33468c" +checksum = "1eba9cb290dbef9542175ed5da58da349d9eb0efb4a2d41942e530e7c775a81b" dependencies = [ "anyhow", "bit-vec", @@ -9281,9 +9282,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cafeec1150ae91f1a37c8f0dce6b71b92b93e0c4153d32b4c37e2fd71bce2f" +checksum = "6d02f1226c102c9ec0745fd65e51f0fc388ef8f20c7df192167462bad0524d2f" dependencies = [ "anyhow", "heck 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 253ffea824b..84148996e89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -216,16 +216,16 @@ zk_evm_1_4_1 = { package = "zk_evm", version = "0.141.0" } zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.0" } # Consensus dependencies. -zksync_concurrency = "=0.1.0-rc.4" -zksync_consensus_bft = "=0.1.0-rc.4" -zksync_consensus_crypto = "=0.1.0-rc.4" -zksync_consensus_executor = "=0.1.0-rc.4" -zksync_consensus_network = "=0.1.0-rc.4" -zksync_consensus_roles = "=0.1.0-rc.4" -zksync_consensus_storage = "=0.1.0-rc.4" -zksync_consensus_utils = "=0.1.0-rc.4" -zksync_protobuf = "=0.1.0-rc.4" -zksync_protobuf_build = "=0.1.0-rc.4" +zksync_concurrency = "=0.1.0-rc.5" +zksync_consensus_bft = "=0.1.0-rc.5" +zksync_consensus_crypto = "=0.1.0-rc.5" +zksync_consensus_executor = "=0.1.0-rc.5" +zksync_consensus_network = "=0.1.0-rc.5" +zksync_consensus_roles = "=0.1.0-rc.5" +zksync_consensus_storage = "=0.1.0-rc.5" +zksync_consensus_utils = "=0.1.0-rc.5" +zksync_protobuf = "=0.1.0-rc.5" +zksync_protobuf_build = "=0.1.0-rc.5" # "Local" dependencies zksync_multivm = { version = "0.1.0", path = "core/lib/multivm" } diff --git a/core/lib/dal/src/consensus_dal.rs b/core/lib/dal/src/consensus_dal.rs index 28559e8a62d..15c4c18b5d8 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal.rs @@ -536,6 +536,7 @@ impl ConsensusDal<'_, '_> { } .await? else { + tracing::info!(%genesis.first_block, "genesis block not found"); return Ok(None); }; Ok(Some(AttestationStatus { diff --git a/core/node/consensus/src/config.rs b/core/node/consensus/src/config.rs index a46b1ab5afa..c2fa1347206 100644 --- a/core/node/consensus/src/config.rs +++ b/core/node/consensus/src/config.rs @@ -147,5 +147,6 @@ pub(super) fn executor( rpc, // TODO: Add to configuration debug_page: None, + batch_poll_interval: time::Duration::seconds(1), }) } diff --git a/core/node/consensus/src/en.rs b/core/node/consensus/src/en.rs index d14893042f5..fe816938685 100644 --- a/core/node/consensus/src/en.rs +++ b/core/node/consensus/src/en.rs @@ -1,8 +1,14 @@ use anyhow::Context as _; +use async_trait::async_trait; use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; -use zksync_consensus_executor as executor; +use zksync_consensus_executor::{ + self as executor, + attestation::{AttestationStatusClient, AttestationStatusRunner}, +}; +use zksync_consensus_network::gossip; use zksync_consensus_roles::validator; use zksync_consensus_storage::{BatchStore, BlockStore}; +use zksync_dal::consensus_dal; use zksync_node_sync::{ fetcher::FetchedBlock, sync_action::ActionQueueSender, MainNodeClient, SyncState, }; @@ -47,6 +53,7 @@ impl EN { // Initialize genesis. let genesis = self.fetch_genesis(ctx).await.wrap("fetch_genesis()")?; + let genesis_hash = genesis.hash(); let mut conn = self.pool.connection(ctx).await.wrap("connection()")?; conn.try_update_genesis(ctx, &genesis) @@ -99,6 +106,18 @@ impl EN { .wrap("BatchStore::new()")?; s.spawn_bg(async { Ok(runner.run(ctx).await?) }); + let (attestation_status, runner) = { + AttestationStatusRunner::init( + ctx, + Box::new(MainNodeAttestationStatus(self.client.clone())), + time::Duration::seconds(5), + genesis_hash, + ) + .await + .wrap("AttestationStatusRunner::init()")? + }; + s.spawn_bg(async { Ok(runner.run(ctx).await?) }); + let executor = executor::Executor { config: config::executor(&cfg, &secrets)?, block_store, @@ -111,7 +130,9 @@ impl EN { payload_manager: Box::new(store.clone()), }), attester, + attestation_status, }; + tracing::info!("running the external node executor"); executor.run(ctx).await?; Ok(()) @@ -239,3 +260,34 @@ impl EN { Ok(()) } } + +/// Wrapper to call [MainNodeClient::fetch_attestation_status] and adapt the return value to [AttestationStatusClient]. +struct MainNodeAttestationStatus(Box>); + +#[async_trait] +impl AttestationStatusClient for MainNodeAttestationStatus { + async fn attestation_status( + &self, + ctx: &ctx::Ctx, + ) -> ctx::Result> { + match ctx.wait(self.0.fetch_attestation_status()).await? { + Ok(Some(status)) => { + // If this fails the AttestationStatusRunner will log it an retry it later, + // but it won't stop the whole node. + let status: consensus_dal::AttestationStatus = + zksync_protobuf::serde::deserialize(&status.0) + .context("deserialize(AttestationStatus")?; + let status = gossip::AttestationStatus { + genesis: status.genesis, + next_batch_to_attest: status.next_batch_to_attest, + }; + Ok(Some(status)) + } + Ok(None) => Ok(None), + Err(err) => { + tracing::warn!("AttestationStatus call to main node HTTP RPC failed: {err}"); + Ok(None) + } + } + } +} diff --git a/core/node/consensus/src/mn.rs b/core/node/consensus/src/mn.rs index 29cacf7a548..b5e76afd63e 100644 --- a/core/node/consensus/src/mn.rs +++ b/core/node/consensus/src/mn.rs @@ -1,7 +1,7 @@ use anyhow::Context as _; -use zksync_concurrency::{ctx, error::Wrap as _, scope}; +use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; use zksync_config::configs::consensus::{ConsensusConfig, ConsensusSecrets}; -use zksync_consensus_executor::{self as executor, Attester}; +use zksync_consensus_executor::{self as executor, attestation::AttestationStatusRunner, Attester}; use zksync_consensus_roles::validator; use zksync_consensus_storage::{BatchStore, BlockStore}; @@ -61,6 +61,18 @@ pub async fn run_main_node( .wrap("BatchStore::new()")?; s.spawn_bg(runner.run(ctx)); + let (attestation_status, runner) = { + AttestationStatusRunner::init_from_store( + ctx, + batch_store.clone(), + time::Duration::seconds(1), + block_store.genesis().hash(), + ) + .await + .wrap("AttestationStatusRunner::init_from_store()")? + }; + s.spawn_bg(runner.run(ctx)); + let executor = executor::Executor { config: config::executor(&cfg, &secrets)?, block_store, @@ -71,7 +83,10 @@ pub async fn run_main_node( payload_manager: Box::new(store.clone()), }), attester, + attestation_status, }; + + tracing::info!("running the main node executor"); executor.run(ctx).await }) .await diff --git a/core/node/consensus/src/storage/connection.rs b/core/node/consensus/src/storage/connection.rs index 8c8992b4d01..0e2039ae6bc 100644 --- a/core/node/consensus/src/storage/connection.rs +++ b/core/node/consensus/src/storage/connection.rs @@ -435,4 +435,15 @@ impl<'a> Connection<'a> { last, }) } + + /// Wrapper for `consensus_dal().attestation_status()`. + pub async fn attestation_status( + &mut self, + ctx: &ctx::Ctx, + ) -> ctx::Result> { + Ok(ctx + .wait(self.0.consensus_dal().attestation_status()) + .await? + .context("attestation_status()")?) + } } diff --git a/core/node/consensus/src/storage/store.rs b/core/node/consensus/src/storage/store.rs index 70744988390..0e08811c237 100644 --- a/core/node/consensus/src/storage/store.rs +++ b/core/node/consensus/src/storage/store.rs @@ -523,44 +523,18 @@ impl storage::PersistentBatchStore for Store { self.batches_persisted.clone() } - /// Get the earliest L1 batch number which has to be signed by attesters. - async fn earliest_batch_number_to_sign( + /// Get the next L1 batch number which has to be signed by attesters. + async fn next_batch_to_attest( &self, ctx: &ctx::Ctx, ) -> ctx::Result> { - // This is the rough roadmap of how this logic will evolve: - // 1. Make best effort at gossiping and collecting votes; the `BatchVotes` in consensus only considers the last vote per attesters. - // Still, we can re-sign more than the last batch, anticipating step 2. - // 2. Ask the Main Node what is the earliest batch number that it still expects votes for (ie. what is the last submission + 1). - // 3. Change `BatchVotes` to handle multiple pending batch numbers, anticipating that batch intervals might decrease dramatically. - // 4. Once QC is required to submit to L1, Look at L1 to figure out what is the last submission, and sign after that. - - // Originally this method returned all unsigned batch numbers by doing a DAL query, but we decided it should be okay and cheap - // to resend signatures for already signed batches, and we don't have to worry about skipping them. Because of that, we also - // didn't think it makes sense to query the database for the earliest unsigned batch *after* the submission, because we might - // as well just re-sign everything. Until we have a way to argue about the "last submission" we just re-sign the last 10 to - // try to produce as many QCs as the voting register allows, within reason. - - // The latest decision is not to store batches with gaps between in the database *of the main node*. - // Once we have an API to serve to external nodes the earliest number the main node wants them to sign, - // we can get rid of this method: on the main node we can sign from what `last_batch_qc` returns, and - // while external nodes we can go from whatever the API returned. - - const NUM_BATCHES_TO_SIGN: u64 = 10; - - let Some(last_batch_number) = self + Ok(self .conn(ctx) .await? - .get_last_batch_number(ctx) + .attestation_status(ctx) .await - .wrap("get_last_batch_number")? - else { - return Ok(None); - }; - - Ok(Some(attester::BatchNumber( - last_batch_number.0.saturating_sub(NUM_BATCHES_TO_SIGN), - ))) + .wrap("next_batch_to_attest")? + .map(|s| s.next_batch_to_attest)) } /// Get the L1 batch QC from storage with the highest number. @@ -603,16 +577,21 @@ impl storage::PersistentBatchStore for Store { ctx: &ctx::Ctx, number: attester::BatchNumber, ) -> ctx::Result> { - let Some(hash) = self - .conn(ctx) - .await? - .batch_hash(ctx, number) - .await - .wrap("batch_hash()")? - else { + let mut conn = self.conn(ctx).await?; + + let Some(hash) = conn.batch_hash(ctx, number).await.wrap("batch_hash()")? else { return Ok(None); }; - Ok(Some(attester::Batch { number, hash })) + + let Some(genesis) = conn.genesis(ctx).await.wrap("genesis()")? else { + return Ok(None); + }; + + Ok(Some(attester::Batch { + number, + hash, + genesis: genesis.hash(), + })) } /// Returns the QC of the batch with the given number. diff --git a/core/node/consensus/src/tests.rs b/core/node/consensus/src/tests.rs index 27c3a7175c7..8e1594393ea 100644 --- a/core/node/consensus/src/tests.rs +++ b/core/node/consensus/src/tests.rs @@ -616,8 +616,16 @@ async fn test_with_pruning(version: ProtocolVersionId) { .wait_for_batch(ctx, validator.last_sealed_batch()) .await?; + // The main node is not supposed to be pruned. In particular `ConsensusDal::attestation_status` + // does not look for where the last prune happened at, and thus if we prune the block genesis + // points at, we might never be able to start the Executor. + tracing::info!("Wait until the external node has all the batches we want to prune"); + node_pool + .wait_for_batch(ctx, to_prune.next()) + .await + .context("wait_for_batch()")?; tracing::info!("Prune some blocks and sync more"); - validator_pool + node_pool .prune_batches(ctx, to_prune) .await .context("prune_batches")?; @@ -725,9 +733,14 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { let mut conn = pool.connection(ctx).await?; let number = status.next_batch_to_attest; let hash = conn.batch_hash(ctx, number).await?.unwrap(); + let genesis = conn.genesis(ctx).await?.unwrap().hash(); let cert = attester::BatchQC { signatures: attester::MultiSig::default(), - message: attester::Batch { number, hash }, + message: attester::Batch { + number, + hash, + genesis, + }, }; conn.insert_batch_certificate(ctx, &cert) .await diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 54e60640d7b..cdeb031c4e6 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -7722,9 +7722,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50302b77192891256d180ff2551dc0c3bc4144958b49e9a16c50a0dc218958ba" +checksum = "4cf1d63d29e467457781bc08763c0348ab86c7a14713df7af5e6cc3ba88632c3" dependencies = [ "anyhow", "once_cell", @@ -7757,9 +7757,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5cb8ed0d59593f6147085b77142628e459ba673aa4d48fce064d5b96e31eb36" +checksum = "037bdb8bf5543676306b2faaa6dc60197a3f9335bbd1af1765d0eae4312ed427" dependencies = [ "anyhow", "blst", @@ -7781,9 +7781,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffe3e47d99eb943eb94f2f5c9d929b1192bf3e8d1434de0fa6f0090f9c1197e" +checksum = "5ade360294fd4b8191adb24b034c9dd87742c70d1859a2a405d24f1d1fb5ecaf" dependencies = [ "anyhow", "bit-vec", @@ -7803,9 +7803,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae9a0ec64ce9c0af346e50cc87dc257c30259101ce9675b408cb883e096087" +checksum = "632a5a54e7359ff29ca34185859c766dab2d531e01f583d0b1bff343249254ef" dependencies = [ "anyhow", "async-trait", @@ -7823,9 +7823,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24dc6135abeefa80f617eb2903fe43d137d362bf673f0651b4894b17069d1fb1" +checksum = "d12966b4cfa166abbc1d702640cefea59fa15f8509e2c959f06e2cfde97ef429" dependencies = [ "anyhow", "rand 0.8.5", @@ -8133,9 +8133,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e7c7820f290db565a1b4ff73aa1175cd7d31498fca8d859eb5aceebd33468c" +checksum = "1eba9cb290dbef9542175ed5da58da349d9eb0efb4a2d41942e530e7c775a81b" dependencies = [ "anyhow", "bit-vec", @@ -8154,9 +8154,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cafeec1150ae91f1a37c8f0dce6b71b92b93e0c4153d32b4c37e2fd71bce2f" +checksum = "6d02f1226c102c9ec0745fd65e51f0fc388ef8f20c7df192167462bad0524d2f" dependencies = [ "anyhow", "heck 0.5.0", diff --git a/zk_toolbox/Cargo.lock b/zk_toolbox/Cargo.lock index cc2640f1f02..1604963d4bf 100644 --- a/zk_toolbox/Cargo.lock +++ b/zk_toolbox/Cargo.lock @@ -6347,9 +6347,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50302b77192891256d180ff2551dc0c3bc4144958b49e9a16c50a0dc218958ba" +checksum = "4cf1d63d29e467457781bc08763c0348ab86c7a14713df7af5e6cc3ba88632c3" dependencies = [ "anyhow", "once_cell", @@ -6381,9 +6381,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24dc6135abeefa80f617eb2903fe43d137d362bf673f0651b4894b17069d1fb1" +checksum = "d12966b4cfa166abbc1d702640cefea59fa15f8509e2c959f06e2cfde97ef429" dependencies = [ "anyhow", "rand", @@ -6432,9 +6432,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e7c7820f290db565a1b4ff73aa1175cd7d31498fca8d859eb5aceebd33468c" +checksum = "1eba9cb290dbef9542175ed5da58da349d9eb0efb4a2d41942e530e7c775a81b" dependencies = [ "anyhow", "bit-vec", @@ -6453,9 +6453,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.1.0-rc.4" +version = "0.1.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cafeec1150ae91f1a37c8f0dce6b71b92b93e0c4153d32b4c37e2fd71bce2f" +checksum = "6d02f1226c102c9ec0745fd65e51f0fc388ef8f20c7df192167462bad0524d2f" dependencies = [ "anyhow", "heck", diff --git a/zk_toolbox/Cargo.toml b/zk_toolbox/Cargo.toml index e1b11d8495b..8175601f32d 100644 --- a/zk_toolbox/Cargo.toml +++ b/zk_toolbox/Cargo.toml @@ -30,7 +30,7 @@ types = { path = "crates/types" } zksync_config = { path = "../core/lib/config" } zksync_protobuf_config = { path = "../core/lib/protobuf_config" } zksync_basic_types = { path = "../core/lib/basic_types" } -zksync_protobuf = "=0.1.0-rc.4" +zksync_protobuf = "=0.1.0-rc.5" # External dependencies anyhow = "1.0.82" @@ -47,7 +47,11 @@ rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9" -sqlx = { version = "0.8.0", features = ["runtime-tokio", "migrate", "postgres"] } +sqlx = { version = "0.8.0", features = [ + "runtime-tokio", + "migrate", + "postgres", +] } strum = { version = "0.26", features = ["derive"] } thiserror = "1.0.57" tokio = { version = "1.37", features = ["full"] }