test: adds mock ConnectionManager interface + watcher failure test cases (#314)

buddies-main-deployment
Luke Tchang 4 years ago committed by GitHub
parent 5ce1430ae0
commit c4b34fb2df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 426
      rust/Cargo.lock
  2. 2
      rust/optics-base/src/replica.rs
  3. 53
      rust/optics-base/src/xapp.rs
  4. 4
      rust/optics-core/src/lib.rs
  5. 4
      rust/optics-test/src/mocks/mod.rs
  6. 114
      rust/optics-test/src/mocks/xapp.rs
  7. 185
      rust/watcher/src/watcher.rs

426
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -28,7 +28,7 @@ impl Replicas {
if let Replicas::Mock(replica) = self { if let Replicas::Mock(replica) = self {
replica.checkpoint(); replica.checkpoint();
} else { } else {
panic!("Home should be mock variant!"); panic!("Replica should be mock variant!");
} }
} }
} }

@ -5,16 +5,32 @@ use optics_core::{
}; };
use optics_ethereum::EthereumConnectionManager; use optics_ethereum::EthereumConnectionManager;
use optics_test::mocks::MockConnectionManagerContract;
/// Replica type /// Replica type
#[derive(Debug)] #[derive(Debug)]
pub enum ConnectionManagers { pub enum ConnectionManagers {
/// Ethereum replica contract /// Ethereum connection manager contract
Ethereum(Box<dyn ConnectionManager>), Ethereum(Box<dyn ConnectionManager>),
/// Other replica variant /// Mock connection manager contract
Mock(Box<MockConnectionManagerContract>),
/// Other connection manager variant
Other(Box<dyn ConnectionManager>), Other(Box<dyn ConnectionManager>),
} }
impl ConnectionManagers {
/// Calls checkpoint on mock variant. Should
/// only be used during tests.
#[doc(hidden)]
pub fn checkpoint(&mut self) {
if let ConnectionManagers::Mock(connection_manager) = self {
connection_manager.checkpoint();
} else {
panic!("ConnectionManager should be mock variant!");
}
}
}
impl<M> From<EthereumConnectionManager<M>> for ConnectionManagers impl<M> From<EthereumConnectionManager<M>> for ConnectionManagers
where where
M: ethers::providers::Middleware + 'static, M: ethers::providers::Middleware + 'static,
@ -24,6 +40,12 @@ where
} }
} }
impl From<MockConnectionManagerContract> for ConnectionManagers {
fn from(mock_connection_manager: MockConnectionManagerContract) -> Self {
ConnectionManagers::Mock(Box::new(mock_connection_manager))
}
}
impl From<Box<dyn ConnectionManager>> for ConnectionManagers { impl From<Box<dyn ConnectionManager>> for ConnectionManagers {
fn from(connection_manager: Box<dyn ConnectionManager>) -> Self { fn from(connection_manager: Box<dyn ConnectionManager>) -> Self {
ConnectionManagers::Other(connection_manager) ConnectionManagers::Other(connection_manager)
@ -35,6 +57,7 @@ impl ConnectionManager for ConnectionManagers {
fn local_domain(&self) -> u32 { fn local_domain(&self) -> u32 {
match self { match self {
ConnectionManagers::Ethereum(connection_manager) => connection_manager.local_domain(), ConnectionManagers::Ethereum(connection_manager) => connection_manager.local_domain(),
ConnectionManagers::Mock(connection_manager) => connection_manager.local_domain(),
ConnectionManagers::Other(connection_manager) => connection_manager.local_domain(), ConnectionManagers::Other(connection_manager) => connection_manager.local_domain(),
} }
} }
@ -44,6 +67,9 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.is_owner(address).await connection_manager.is_owner(address).await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager.is_owner(address).await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.is_owner(address).await connection_manager.is_owner(address).await
} }
@ -55,6 +81,9 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.is_replica(address).await connection_manager.is_replica(address).await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager.is_replica(address).await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.is_replica(address).await connection_manager.is_replica(address).await
} }
@ -70,6 +99,9 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.watcher_permission(address, domain).await connection_manager.watcher_permission(address, domain).await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager.watcher_permission(address, domain).await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.watcher_permission(address, domain).await connection_manager.watcher_permission(address, domain).await
} }
@ -87,6 +119,11 @@ impl ConnectionManager for ConnectionManagers {
.owner_enroll_replica(replica, domain) .owner_enroll_replica(replica, domain)
.await .await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager
.owner_enroll_replica(replica, domain)
.await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager connection_manager
.owner_enroll_replica(replica, domain) .owner_enroll_replica(replica, domain)
@ -103,6 +140,9 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.owner_unenroll_replica(replica).await connection_manager.owner_unenroll_replica(replica).await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager.owner_unenroll_replica(replica).await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.owner_unenroll_replica(replica).await connection_manager.owner_unenroll_replica(replica).await
} }
@ -114,6 +154,7 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.set_home(home).await connection_manager.set_home(home).await
} }
ConnectionManagers::Mock(connection_manager) => connection_manager.set_home(home).await,
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.set_home(home).await connection_manager.set_home(home).await
} }
@ -132,6 +173,11 @@ impl ConnectionManager for ConnectionManagers {
.set_watcher_permission(watcher, domain, access) .set_watcher_permission(watcher, domain, access)
.await .await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager
.set_watcher_permission(watcher, domain, access)
.await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager connection_manager
.set_watcher_permission(watcher, domain, access) .set_watcher_permission(watcher, domain, access)
@ -148,6 +194,9 @@ impl ConnectionManager for ConnectionManagers {
ConnectionManagers::Ethereum(connection_manager) => { ConnectionManagers::Ethereum(connection_manager) => {
connection_manager.unenroll_replica(signed_failure).await connection_manager.unenroll_replica(signed_failure).await
} }
ConnectionManagers::Mock(connection_manager) => {
connection_manager.unenroll_replica(signed_failure).await
}
ConnectionManagers::Other(connection_manager) => { ConnectionManagers::Other(connection_manager) => {
connection_manager.unenroll_replica(signed_failure).await connection_manager.unenroll_replica(signed_failure).await
} }

@ -404,7 +404,7 @@ impl SignedUpdate {
} }
/// Failure notification produced by watcher /// Failure notification produced by watcher
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct FailureNotification { pub struct FailureNotification {
/// Domain of failed home /// Domain of failed home
pub home_domain: u32, pub home_domain: u32,
@ -442,7 +442,7 @@ impl FailureNotification {
} }
/// Signed failure notification produced by watcher /// Signed failure notification produced by watcher
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct SignedFailureNotification { pub struct SignedFailureNotification {
/// Failure notification /// Failure notification
pub notification: FailureNotification, pub notification: FailureNotification,

@ -4,5 +4,9 @@ pub mod home;
/// Mock replica contract /// Mock replica contract
pub mod replica; pub mod replica;
/// Mock connection manager contract
pub mod xapp;
pub use home::MockHomeContract; pub use home::MockHomeContract;
pub use replica::MockReplicaContract; pub use replica::MockReplicaContract;
pub use xapp::MockConnectionManagerContract;

@ -0,0 +1,114 @@
#![allow(non_snake_case)]
use async_trait::async_trait;
use mockall::*;
use optics_core::{
traits::{ChainCommunicationError, ConnectionManager, TxOutcome},
OpticsIdentifier, SignedFailureNotification,
};
mock! {
pub ConnectionManagerContract {
pub fn _local_domain(&self) -> u32 {}
pub fn _is_owner(&self, address: OpticsIdentifier) -> Result<bool, ChainCommunicationError> {}
pub fn _is_replica(&self, address: OpticsIdentifier) -> Result<bool, ChainCommunicationError> {}
pub fn _watcher_permission(
&self,
address: OpticsIdentifier,
domain: u32,
) -> Result<bool, ChainCommunicationError> {}
pub fn _owner_enroll_replica(
&self,
replica: OpticsIdentifier,
domain: u32,
) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _owner_unenroll_replica(
&self,
replica: OpticsIdentifier,
) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _set_home(&self, home: OpticsIdentifier) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _set_watcher_permission(
&self,
watcher: OpticsIdentifier,
domain: u32,
access: bool,
) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _unenroll_replica(
&self,
signed_failure: &SignedFailureNotification,
) -> Result<TxOutcome, ChainCommunicationError> {}
}
}
impl std::fmt::Debug for MockConnectionManagerContract {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MockConnectionManagerContract")
}
}
#[async_trait]
impl ConnectionManager for MockConnectionManagerContract {
fn local_domain(&self) -> u32 {
self._local_domain()
}
async fn is_owner(&self, address: OpticsIdentifier) -> Result<bool, ChainCommunicationError> {
self._is_owner(address)
}
async fn is_replica(&self, address: OpticsIdentifier) -> Result<bool, ChainCommunicationError> {
self._is_replica(address)
}
async fn watcher_permission(
&self,
address: OpticsIdentifier,
domain: u32,
) -> Result<bool, ChainCommunicationError> {
self._watcher_permission(address, domain)
}
async fn owner_enroll_replica(
&self,
replica: OpticsIdentifier,
domain: u32,
) -> Result<TxOutcome, ChainCommunicationError> {
self._owner_enroll_replica(replica, domain)
}
async fn owner_unenroll_replica(
&self,
replica: OpticsIdentifier,
) -> Result<TxOutcome, ChainCommunicationError> {
self._owner_unenroll_replica(replica)
}
async fn set_home(&self, home: OpticsIdentifier) -> Result<TxOutcome, ChainCommunicationError> {
self._set_home(home)
}
async fn set_watcher_permission(
&self,
watcher: OpticsIdentifier,
domain: u32,
access: bool,
) -> Result<TxOutcome, ChainCommunicationError> {
self._set_watcher_permission(watcher, domain, access)
}
async fn unenroll_replica(
&self,
signed_failure: &SignedFailureNotification,
) -> Result<TxOutcome, ChainCommunicationError> {
self._unenroll_replica(signed_failure)
}
}

@ -439,8 +439,12 @@ mod test {
use ethers::core::types::H256; use ethers::core::types::H256;
use ethers::signers::LocalWallet; use ethers::signers::LocalWallet;
use optics_core::{traits::DoubleUpdate, Update}; use optics_base::{agent::AgentCore, replica::Replicas};
use optics_test::{mocks::MockHomeContract, test_utils}; use optics_core::{traits::DoubleUpdate, SignedFailureNotification, Update};
use optics_test::{
mocks::{MockConnectionManagerContract, MockHomeContract, MockReplicaContract},
test_utils,
};
use super::*; use super::*;
@ -641,4 +645,181 @@ mod test {
}) })
.await .await
} }
#[tokio::test]
async fn it_fails_contracts_and_unenrolls_replicas_on_double_update() {
test_utils::run_test_db(|db| async move {
let home_domain = 1;
let updater: LocalWallet =
"1111111111111111111111111111111111111111111111111111111111111111"
.parse()
.unwrap();
// Double update setup
let first_root = H256::from([1; 32]);
let second_root = H256::from([2; 32]);
let bad_second_root = H256::from([3; 32]);
let update = Update {
home_domain,
previous_root: first_root,
new_root: second_root,
}
.sign_with(&updater)
.await
.expect("!sign");
let bad_update = Update {
home_domain,
previous_root: first_root,
new_root: bad_second_root,
}
.sign_with(&updater)
.await
.expect("!sign");
let double = DoubleUpdate(update, bad_update);
let signed_failure = FailureNotification {
home_domain,
updater: updater.address().into(),
}
.sign_with(&updater)
.await
.expect("!sign");
// Contract setup
let mut mock_connection_manager_1 = MockConnectionManagerContract::new();
let mut mock_connection_manager_2 = MockConnectionManagerContract::new();
let mut mock_home = MockHomeContract::new();
let mut mock_replica_1 = MockReplicaContract::new();
let mut mock_replica_2 = MockReplicaContract::new();
// Home and replica expectations
{
// home.local_domain returns `home_domain`
mock_home
.expect__local_domain()
.times(1)
.return_once(move || home_domain);
// home.updater returns `updater` address
let updater = updater.clone();
mock_home
.expect__updater()
.times(1)
.return_once(move || Ok(updater.address().into()));
// home.double_update called once
let double = double.clone();
mock_home
.expect__double_update()
.withf(move |d: &DoubleUpdate| *d == double)
.times(1)
.return_once(move |_| {
Ok(TxOutcome {
txid: H256::default(),
executed: true,
})
});
}
{
// replica_1.double_update called once
let double = double.clone();
mock_replica_1
.expect__double_update()
.withf(move |d: &DoubleUpdate| *d == double)
.times(1)
.return_once(move |_| {
Ok(TxOutcome {
txid: H256::default(),
executed: true,
})
});
}
{
// replica_2.double_update called once
let double = double.clone();
mock_replica_2
.expect__double_update()
.withf(move |d: &DoubleUpdate| *d == double)
.times(1)
.return_once(move |_| {
Ok(TxOutcome {
txid: H256::default(),
executed: true,
})
});
}
// Connection manager expectations
{
// connection_manager_1.unenroll_replica called once
let signed_failure = signed_failure.clone();
mock_connection_manager_1
.expect__unenroll_replica()
.withf(move |f: &SignedFailureNotification| *f == signed_failure)
.times(1)
.return_once(move |_| {
Ok(TxOutcome {
txid: H256::default(),
executed: true,
})
});
}
{
// connection_manager_2.unenroll_replica called once
let signed_failure = signed_failure.clone();
mock_connection_manager_2
.expect__unenroll_replica()
.withf(move |f: &SignedFailureNotification| *f == signed_failure)
.times(1)
.return_once(move |_| {
Ok(TxOutcome {
txid: H256::default(),
executed: true,
})
});
}
// Watcher agent setup
let connection_managers: Vec<ConnectionManagers> = vec![
mock_connection_manager_1.into(),
mock_connection_manager_2.into(),
];
let home = Arc::new(mock_home.into());
let replica_1 = Arc::new(mock_replica_1.into());
let replica_2 = Arc::new(mock_replica_2.into());
let mut replica_map: HashMap<String, Arc<Replicas>> = HashMap::new();
replica_map.insert("replica_1".into(), replica_1);
replica_map.insert("replica_2".into(), replica_2);
let core = AgentCore {
home,
replicas: replica_map,
db: Arc::new(db),
};
let mut watcher = Watcher::new(updater.into(), 1, connection_managers, core);
watcher.handle_failure(&double).await;
// Checkpoint connection managers
for connection_manager in watcher.connection_managers.iter_mut() {
connection_manager.checkpoint();
}
// Checkpoint home
let mock_home = Arc::get_mut(&mut watcher.core.home).unwrap();
mock_home.checkpoint();
// Checkpoint replicas
for replica in watcher.core.replicas.values_mut() {
Arc::get_mut(replica).unwrap().checkpoint();
}
})
.await
}
} }

Loading…
Cancel
Save