Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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() {
Comment thread
playX18 marked this conversation as resolved.
Outdated
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
Comment thread
playX18 marked this conversation as resolved.
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