Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ethexe/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ gear-workspace-hack.workspace = true
libp2p-swarm-test = { version = "0.6.0", default-features = false, features = ["tokio"] }
tokio = { workspace = true, features = ["full", "test-util"] }
tracing-subscriber.workspace = true
proptest = { workspace = true }
ethexe-common = { workspace = true, features = ["mock"] }
ethexe-db = { workspace = true, features = ["mock"] }
6 changes: 3 additions & 3 deletions ethexe/network/src/injected.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,10 @@ impl NetworkBehaviour for Behaviour {
mod tests {
use super::*;
use crate::{
utils::tests::init_logger,
utils::tests::{arb_value, init_logger},
validator::discovery::{SignedValidatorIdentity, ValidatorAddresses, ValidatorIdentity},
};
use ethexe_common::{injected::InjectedTransaction, mock::Mock};
use ethexe_common::injected::InjectedTransaction;
use gsigner::secp256k1::{Secp256k1SignerExt, Signer};
use libp2p::{
Swarm, Transport,
Expand All @@ -420,7 +420,7 @@ mod tests {
let signer = Signer::memory();
let pub_key = signer.generate().unwrap();

let tx = InjectedTransaction::mock(());
let tx = arb_value::<InjectedTransaction>(());
let tx = signer.signed_message(pub_key, tx, None).unwrap();

AddressedInjectedTransaction { recipient, tx }
Expand Down
6 changes: 3 additions & 3 deletions ethexe/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -845,11 +845,11 @@ mod tests {
use super::*;
use crate::{
db_sync::{ExternalDataProvider, tests::fill_data_provider},
utils::tests::init_logger,
utils::tests::{arb_value, init_logger},
};
use assert_matches::assert_matches;
use async_trait::async_trait;
use ethexe_common::{BlockHeader, ProtocolTimelines, db::*, gear::CodeState, mock::*};
use ethexe_common::{BlockHeader, ProtocolTimelines, db::*, gear::CodeState};
use ethexe_db::Database;
use gprimitives::{ActorId, CodeId, H256};
use gsigner::secp256k1::Signer;
Expand Down Expand Up @@ -971,7 +971,7 @@ mod tests {

db.set_config(DBConfig {
timelines: TIMELINES,
..DBConfig::mock(())
..arb_value::<DBConfig>(())
});

let key = signer.generate().unwrap();
Expand Down
15 changes: 15 additions & 0 deletions ethexe/network/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@ pub(crate) mod tests {
utils::{ConnectionMap, ExponentialBackoffInterval},
};
use libp2p::swarm::ConnectionId;
use proptest::{
arbitrary::Arbitrary,
strategy::{Strategy, ValueTree},
test_runner::TestRunner,
};
use std::{collections::HashSet, future, time::Duration};
use tokio::time;
use tracing_subscriber::EnvFilter;
Expand All @@ -423,6 +428,16 @@ pub(crate) mod tests {
.try_init();
}

pub fn arb_value<T>(args: impl Into<T::Parameters>) -> T
where
T: Arbitrary + 'static,
{
T::arbitrary_with(args.into())
.new_tree(&mut TestRunner::default())
Comment thread
ark0f marked this conversation as resolved.
.expect("strategy must produce a value")
.current()
}

#[test]
fn connection_map_key_cleared() {
let mut map = ConnectionMap::without_limits();
Expand Down
41 changes: 41 additions & 0 deletions ethexe/network/src/validator/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ mod tests {
swarm::{SwarmEvent, behaviour::ExternalAddrConfirmed},
};
use libp2p_swarm_test::SwarmExt;
use proptest::{prelude::*, test_runner::Config as ProptestConfig};
use std::sync::Arc;
use tokio::time;

Expand Down Expand Up @@ -800,6 +801,32 @@ mod tests {
.expect("failed to sign validator identity")
}

fn signed_identity_strategy() -> impl Strategy<Value = SignedValidatorIdentity> {
(any::<[u8; 32]>(), any::<[u8; 32]>(), any::<u128>()).prop_filter_map(
"valid secp256k1 validator and network keys",
|(validator_seed, mut network_seed, creation_time)| {
let validator_private_key = PrivateKey::from_seed(validator_seed).ok()?;
let signer = Signer::memory();
let validator_key = signer.import(validator_private_key).ok()?;

let network_secret =
libp2p::identity::secp256k1::SecretKey::try_from_bytes(&mut network_seed)
.ok()?;
let network_keypair =
Keypair::from(libp2p::identity::secp256k1::Keypair::from(network_secret));
let identity = ValidatorIdentity {
addresses: ValidatorAddresses::new(
network_keypair.public().to_peer_id(),
test_addr(),
),
creation_time,
};

identity.sign(&signer, validator_key, &network_keypair).ok()
},
)
}

#[test]
fn encode_decode_identity() {
Copy link
Copy Markdown
Member

@ark0f ark0f Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can be removed since proptest fully covers it

let signer = Signer::memory();
Expand All @@ -816,6 +843,20 @@ mod tests {
assert_eq!(identity, decoded_identity);
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]

#[test]
fn proptest_signed_validator_identity_encode_decode(
identity in signed_identity_strategy(),
) {
let decoded_identity =
SignedValidatorIdentity::decode(&mut &identity.encode()[..]).unwrap();

prop_assert_eq!(identity, decoded_identity);
}
}

#[test]
fn different_peer_ids_in_identity() {
let signer = Signer::memory();
Expand Down
132 changes: 116 additions & 16 deletions ethexe/network/src/validator/topic.rs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, remove or rewrite proptests. The only property we want to test is relationship between era index in snapshot and message itself

Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,18 @@ impl ValidatorTopic {
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::tests::arb_value;
use assert_matches::assert_matches;
use ethexe_common::{
Announce,
Announce, HashOf,
ecdsa::SignedData,
gear_core::{message::ReplyCode, rpc::ReplyInfo},
injected::Promise,
mock::Mock,
network::{SignedValidatorMessage, ValidatorMessage},
};
use gsigner::secp256k1::{Secp256k1SignerExt, Signer};
use gsigner::secp256k1::{PrivateKey, Secp256k1SignerExt, Signer};
use nonempty::{NonEmpty, nonempty};
use proptest::{prelude::*, test_runner::Config as ProptestConfig};

const CHAIN_HEAD_ERA: u64 = 10;

Expand All @@ -357,24 +359,25 @@ mod tests {
)
}

fn new_validator_message(era_index: u64) -> VerifiedValidatorMessage {
let signer = Signer::memory();
let pub_key = signer.generate().unwrap();

signer
.signed_data(
pub_key,
ValidatorMessage {
era_index,
payload: Announce::mock(()),
},
None,
)
fn validator_message_from_private_key(
private_key: PrivateKey,
era_index: u64,
payload: Announce,
) -> VerifiedValidatorMessage {
SignedData::create(&private_key, ValidatorMessage { era_index, payload })
.map(SignedValidatorMessage::from)
.unwrap()
.into_verified()
}

fn new_validator_message(era_index: u64) -> VerifiedValidatorMessage {
validator_message_from_private_key(
PrivateKey::random(),
era_index,
arb_value::<Announce>(()),
)
}

fn signed_promise() -> SignedPromise {
let signer = Signer::memory();
let pub_key = signer.generate().unwrap();
Expand All @@ -390,6 +393,103 @@ mod tests {
signer.signed_message(pub_key, promise, None).unwrap()
}

fn test_announce() -> Announce {
Announce {
block_hash: Default::default(),
parent: HashOf::zero(),
gas_allowance: Some(100),
injected_transactions: Vec::new(),
}
}

#[derive(Debug, Clone, Copy)]
enum EraRelation {
TooOld(u64),
Old,
Current,
Next,
TooNew(u64),
}

impl EraRelation {
fn message_era(self, snapshot_era: u64) -> u64 {
match self {
Self::TooOld(delta) => snapshot_era - delta,
Self::Old => snapshot_era - 1,
Self::Current => snapshot_era,
Self::Next => snapshot_era + 1,
Self::TooNew(delta) => snapshot_era + delta,
}
}

fn expected_verification(self, snapshot_era: u64) -> Result<(), VerifyMessageError> {
let message_era = self.message_era(snapshot_era);

match self {
Self::TooOld(_) => Err(VerifyMessageRejectReason::TooOldEra {
expected_era: snapshot_era,
received_era: message_era,
}
.into()),
Self::Old => Err(VerifyMessageIgnoreReason::OldEra {
expected_era: snapshot_era,
received_era: message_era,
}
.into()),
Self::Current => Ok(()),
Self::Next => Err(VerifyMessageCacheReason::NewEra {
expected_era: snapshot_era,
received_era: message_era,
}
.into()),
Self::TooNew(_) => Err(VerifyMessageRejectReason::TooNewEra {
expected_era: snapshot_era,
received_era: message_era,
}
.into()),
}
}
}

fn era_relation_strategy() -> impl Strategy<Value = (u64, EraRelation)> {
(
128u64..(u64::MAX - 128),
prop_oneof![
(2u64..128).prop_map(EraRelation::TooOld).boxed(),
Just(EraRelation::Old).boxed(),
Just(EraRelation::Current).boxed(),
Just(EraRelation::Next).boxed(),
(2u64..128).prop_map(EraRelation::TooNew).boxed(),
],
)
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]

#[test]
fn proptest_message_era_is_checked_against_snapshot_era(
(snapshot_era, relation) in era_relation_strategy(),
) {
let private_key = PrivateKey::from_seed([1; 32]).expect("seed is valid");
let message_era = relation.message_era(snapshot_era);
let message =
validator_message_from_private_key(private_key, message_era, test_announce());
let validator = message.address();
let snapshot = ValidatorListSnapshot {
current_era_index: snapshot_era,
current_validators: nonempty![validator].into(),
next_validators: Some(nonempty![validator].into()),
};
let alice = ValidatorTopic::new(peer_score::Handle::new_test(), Arc::new(snapshot));

prop_assert_eq!(
alice.inner_verify_validator_message(&message),
relation.expected_verification(snapshot_era)
);
}
}

#[test]
fn too_old_era() {
let bob_message = new_validator_message(CHAIN_HEAD_ERA - 2);
Expand Down
Loading