diff --git a/Cargo.lock b/Cargo.lock index 8dd4b568d..c542fb743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3622,6 +3622,14 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "madhouse" +version = "0.2.0" +source = "git+https://github.com/stacks-network/madhouse-rs.git?tag=0.2.1#5d4d51bedf2d56c8ef0643d891fb085df1615c91" +dependencies = [ + "proptest", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -5796,6 +5804,7 @@ dependencies = [ "include_dir", "libp2p", "lru", + "madhouse", "metrics", "metrics-exporter-prometheus", "mockall", @@ -5803,6 +5812,7 @@ dependencies = [ "more-asserts", "p256k1", "polynomial", + "proptest", "prost", "rand", "reqwest 0.11.27", diff --git a/Cargo.toml b/Cargo.toml index 6f6b8c574..dc0a4a456 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = # Crates used only for testing fake = { version = "3.1.0", default-features = false, features = ["derive", "time"] } +madhouse = { git = "https://github.com/stacks-network/madhouse-rs.git", tag = "0.2.1", default-features = false } mockall = { version = "0.13.1", default-features = false } mockito = { version = "1.6.1", default-features = false } more-asserts = { version = "0.3.1", default-features = false } diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 250aec39b..1611678a7 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -68,8 +68,10 @@ tonic-build.workspace = true [dev-dependencies] bitcoincore-rpc.workspace = true +madhouse.workspace = true mockito.workspace = true more-asserts.workspace = true +proptest.workspace = true ripemd.workspace = true sbtc = { workspace = true, features = ["testing"] } # We need this so that we have access to "testing" feature code in our diff --git a/signer/tests/integration/commands/context.rs b/signer/tests/integration/commands/context.rs new file mode 100644 index 000000000..6edbb8bf9 --- /dev/null +++ b/signer/tests/integration/commands/context.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use madhouse::{State, TestContext}; +use signer::{ + storage::{model::EncryptedDkgShares, postgres::PgStore}, + testing::storage::new_test_database, +}; +use tokio::runtime::Runtime; + +#[derive(Clone, Debug)] +pub struct Ctx { + db: PgStore, + runtime: Arc, +} + +impl Ctx { + pub fn new() -> Self { + let runtime = Arc::new(Runtime::new().unwrap()); + let db = runtime.block_on(async { new_test_database().await }); + + Self { db, runtime } + } + + pub fn db(&self) -> &PgStore { + &self.db + } + + pub fn runtime_handle(&self) -> Arc { + self.runtime.clone() + } +} + +impl TestContext for Ctx {} + +#[derive(Debug, Default)] +pub struct TestState { + pub shares: Option, +} + +impl State for TestState {} diff --git a/signer/tests/integration/commands/dkg.rs b/signer/tests/integration/commands/dkg.rs new file mode 100644 index 000000000..958910327 --- /dev/null +++ b/signer/tests/integration/commands/dkg.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use fake::Fake as _; +use fake::Faker; +use madhouse::{Command, CommandWrapper}; +use proptest::prelude::Just; +use proptest::prelude::Strategy; +use proptest::prelude::any; +use rand::SeedableRng; +use secp256k1::{Keypair, rand::rngs::StdRng}; +use signer::storage::DbWrite as _; +use signer::{ + keys::PublicKey, + storage::model::{DkgSharesStatus, EncryptedDkgShares}, +}; + +use super::{Ctx, TestState}; + +pub struct CreateFailedDkgShares { + seed: u64, +} + +impl CreateFailedDkgShares { + fn new(seed: u64) -> Self { + Self { seed } + } +} + +impl Command for CreateFailedDkgShares { + fn check(&self, _state: &TestState) -> bool { + true + } + + fn apply(&self, state: &mut TestState) { + let mut rng = StdRng::seed_from_u64(self.seed); + let aggregate_key: PublicKey = Keypair::new_global(&mut rng).public_key().into(); + let shares = EncryptedDkgShares { + aggregate_key, + dkg_shares_status: DkgSharesStatus::Failed, + started_at_bitcoin_block_height: 0u64.into(), + ..Faker.fake_with_rng(&mut rng) + }; + + state.shares = Some(shares); + } + + fn label(&self) -> String { + "CREATE_FAILED_DKG_SHARES".to_string() + } + + fn build(_ctx: Arc) -> impl Strategy> { + any::().prop_map(|seed| CommandWrapper::new(CreateFailedDkgShares::new(seed))) + } +} + +pub struct WriteDkgShares { + ctx: Arc, +} + +impl WriteDkgShares { + pub fn new(ctx: Arc) -> Self { + Self { ctx } + } +} + +impl Command for WriteDkgShares { + fn check(&self, state: &TestState) -> bool { + state.shares.is_some() + } + + fn apply(&self, state: &mut TestState) { + let shares = state.shares.as_ref().unwrap(); + + self.ctx.runtime_handle().block_on(async { + match self.ctx.db().write_encrypted_dkg_shares(shares).await { + Ok(db_result) => db_result, + Err(e) => { + panic!("Failed to write DKG shares: {}", e); + } + }; + }); + } + + fn label(&self) -> String { + "WRITE_DKG_SHARES".to_string() + } + + fn build(ctx: Arc) -> impl Strategy> { + Just(CommandWrapper::new(WriteDkgShares::new(ctx.clone()))) + } +} diff --git a/signer/tests/integration/commands/mod.rs b/signer/tests/integration/commands/mod.rs new file mode 100644 index 000000000..f698857be --- /dev/null +++ b/signer/tests/integration/commands/mod.rs @@ -0,0 +1,7 @@ +mod context; +mod dkg; +mod test_cases; + +pub use context::{Ctx, TestState}; +pub use dkg::{CreateFailedDkgShares, WriteDkgShares}; +pub use test_cases::VerifyDkgVerificationFailed; diff --git a/signer/tests/integration/commands/test_cases.rs b/signer/tests/integration/commands/test_cases.rs new file mode 100644 index 000000000..08f754c30 --- /dev/null +++ b/signer/tests/integration/commands/test_cases.rs @@ -0,0 +1,52 @@ +use std::sync::Arc; + +use madhouse::{Command, CommandWrapper}; +use proptest::prelude::{Just, Strategy}; +use signer::{error::Error, keys::PublicKeyXOnly, storage::model::DkgSharesStatus}; + +use crate::transaction_signer::validate_dkg_verification_message::TestParams; + +use super::{Ctx, TestState}; + +pub struct VerifyDkgVerificationFailed { + ctx: Arc, +} + +impl VerifyDkgVerificationFailed { + pub fn new(ctx: Arc) -> Self { + Self { ctx } + } +} + +impl Command for VerifyDkgVerificationFailed { + fn check(&self, state: &TestState) -> bool { + state.shares.is_some() + && state.shares.as_ref().unwrap().dkg_shares_status == DkgSharesStatus::Failed + } + + fn apply(&self, state: &mut TestState) { + let aggregate_key = state.shares.as_ref().unwrap().aggregate_key.clone(); + let aggregate_key_x_only: PublicKeyXOnly = aggregate_key.into(); + let params = TestParams::new(aggregate_key_x_only); + + let result = self + .ctx + .runtime_handle() + .block_on(async { params.execute(self.ctx.db()).await.unwrap_err() }); + + assert!(matches!( + result, + Error::DkgVerificationFailed(key) if aggregate_key_x_only == key + )) + } + + fn label(&self) -> String { + "VERIFY_DKG_VERIFICATION_FAILED".to_string() + } + + fn build(ctx: Arc) -> impl Strategy> { + Just(CommandWrapper::new(VerifyDkgVerificationFailed::new( + ctx.clone(), + ))) + } +} diff --git a/signer/tests/integration/main.rs b/signer/tests/integration/main.rs index 155dc8c91..cfdaad519 100644 --- a/signer/tests/integration/main.rs +++ b/signer/tests/integration/main.rs @@ -4,6 +4,7 @@ mod bitcoin_client; mod bitcoin_rpc; mod bitcoin_validation; mod block_observer; +mod commands; mod communication; mod complete_deposit; mod contracts; diff --git a/signer/tests/integration/transaction_signer.rs b/signer/tests/integration/transaction_signer.rs index 0200c7683..99cbbbef2 100644 --- a/signer/tests/integration/transaction_signer.rs +++ b/signer/tests/integration/transaction_signer.rs @@ -721,7 +721,7 @@ async fn max_one_state_machine_per_bitcoin_block_hash_for_dkg() { /// [`MockedTxSigner::validate_dkg_verification_message`] function. See /// [`MockedTxSigner`] for information on the validations that these tests /// are asserting. -mod validate_dkg_verification_message { +pub mod validate_dkg_verification_message { use secp256k1::Keypair; use signer::{ @@ -733,7 +733,8 @@ mod validate_dkg_verification_message { /// Helper struct for testing /// [`MockedTxSigner::validate_dkg_verification_message`]. - struct TestParams { + #[derive(Debug)] + pub struct TestParams { pub new_aggregate_key: PublicKeyXOnly, pub dkg_verification_window: u16, pub bitcoin_chain_tip: BitcoinBlockRef, @@ -756,7 +757,7 @@ mod validate_dkg_verification_message { } impl TestParams { - fn new(new_aggregate_key: PublicKeyXOnly) -> Self { + pub fn new(new_aggregate_key: PublicKeyXOnly) -> Self { Self { new_aggregate_key, ..Self::default() @@ -764,7 +765,7 @@ mod validate_dkg_verification_message { } /// Executes [`MockedTxSigner::validate_dkg_verification_message`] with /// the values in this [`TestParams`] instance. - async fn execute(&self, db: &PgStore) -> Result<(), Error> { + pub async fn execute(&self, db: &PgStore) -> Result<(), Error> { MockedTxSigner::validate_dkg_verification_message::( &db, &self.new_aggregate_key, @@ -850,6 +851,26 @@ mod validate_dkg_verification_message { )) } + use std::sync::Arc; + + use crate::commands::{ + CreateFailedDkgShares, Ctx, VerifyDkgVerificationFailed, WriteDkgShares, + }; + use madhouse::prelude::*; + use proptest::prelude::*; + + #[test] + fn latest_key_in_failed_state_scenario() { + let test_context = Arc::new(Ctx::new()); + + scenario![ + test_context, + CreateFailedDkgShares, + WriteDkgShares, + VerifyDkgVerificationFailed, + ] + } + #[tokio::test] async fn verification_window_elapsed() { let db = testing::storage::new_test_database().await; diff --git a/supply-chain/config.toml b/supply-chain/config.toml index d08ebcc69..753c7d728 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -49,10 +49,6 @@ criteria = "safe-to-deploy" version = "1.1.3" criteria = "safe-to-deploy" -[[exemptions.android-tzdata]] -version = "0.1.1" -criteria = "safe-to-deploy" - [[exemptions.anstyle]] version = "1.0.10" criteria = "safe-to-deploy" @@ -525,10 +521,6 @@ criteria = "safe-to-deploy" version = "0.1.9" criteria = "safe-to-deploy" -[[exemptions.fastrand]] -version = "2.3.0" -criteria = "safe-to-deploy" - [[exemptions.finl_unicode]] version = "1.2.0" criteria = "safe-to-deploy" @@ -1429,10 +1421,6 @@ criteria = "safe-to-deploy" version = "0.2.3" criteria = "safe-to-deploy" -[[exemptions.rustc_version]] -version = "0.4.0" -criteria = "safe-to-deploy" - [[exemptions.rustfmt-wrapper]] version = "0.2.1" criteria = "safe-to-deploy" @@ -1817,10 +1805,6 @@ criteria = "safe-to-deploy" version = "0.1.2" criteria = "safe-to-deploy" -[[exemptions.tinyvec_macros]] -version = "0.1.1" -criteria = "safe-to-deploy" - [[exemptions.tokio]] version = "1.43.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 1085c9e8b..503fa6fad 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -116,6 +116,21 @@ who = "Nick Fitzgerald " criteria = "safe-to-deploy" delta = "0.2.4 -> 0.2.5" +[[audits.bytecodealliance.audits.fastrand]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "2.0.0 -> 2.0.1" +notes = """ +This update had a few doc updates but no otherwise-substantial source code +updates. +""" + +[[audits.bytecodealliance.audits.fastrand]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "2.1.1 -> 2.3.0" +notes = "Minor refactoring, nothing new." + [[audits.bytecodealliance.audits.foldhash]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -363,6 +378,16 @@ without `unsafe`. Skimming the crate everything looks reasonable and what one would expect from idiomatic safe collections in Rust. """ +[[audits.bytecodealliance.audits.tinyvec_macros]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.1.0" +notes = """ +This is a trivial crate which only contains a singular macro definition which is +intended to multiplex across the internal representation of a tinyvec, +presumably. This trivially doesn't contain anything bad. +""" + [[audits.bytecodealliance.audits.tokio-native-tls]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -579,6 +604,16 @@ criteria = "safe-to-deploy" version = "1.0.1" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.fastrand]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.9.0" +notes = """ +`does-not-implement-crypto` is certified because this crate explicitly says +that the RNG here is not cryptographically secure. +""" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.glob]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -1351,6 +1386,13 @@ criteria = "safe-to-deploy" version = "0.2.18" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.android-tzdata]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "Small crate parsing a file. No unsafe code" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.android_system_properties]] who = "Nicolas Silva " criteria = "safe-to-deploy" @@ -1436,6 +1478,25 @@ criteria = "safe-to-deploy" delta = "0.2.3 -> 0.2.4" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.fastrand]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.9.0 -> 2.0.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.fastrand]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "2.0.1 -> 2.1.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.fastrand]] +who = "Chris Martin " +criteria = "safe-to-deploy" +delta = "2.1.0 -> 2.1.1" +notes = "Fairly trivial changes, no chance of security regression." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.fnv]] who = "Bobby Holley " criteria = "safe-to-deploy" @@ -1757,6 +1818,16 @@ version = "1.1.0" notes = "Straightforward crate with no unsafe code, does what it says on the tin." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.rustc_version]] +who = "Nika Layzell " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = """ +Use of powerful capabilities is limited to invoking `rustc -vV` to get version +information for parsing version information. +""" +aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" + [[audits.mozilla.audits.sha2]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -1883,6 +1954,12 @@ criteria = "safe-to-deploy" delta = "0.7.4 -> 0.7.6" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.tinyvec_macros]] +who = "Drew Willcoxon " +criteria = "safe-to-deploy" +delta = "0.1.0 -> 0.1.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.unicode-bidi]] who = "Makoto Kato " criteria = "safe-to-deploy"