Browse Source

Merge #791

791: Test collaborative settlement r=klochowicz a=klochowicz



Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>
feature/reconnect-button
bors[bot] 3 years ago
committed by GitHub
parent
commit
0672c63b7e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      daemon/src/model/cfd.rs
  2. 110
      daemon/tests/happy_path.rs
  3. 29
      daemon/tests/harness/bdk.rs
  4. 18
      daemon/tests/harness/mocks/mod.rs
  5. 86
      daemon/tests/harness/mod.rs

2
daemon/src/model/cfd.rs

@ -1632,7 +1632,7 @@ pub struct CollaborativeSettlement {
impl CollaborativeSettlement {
pub fn new(tx: Transaction, own_script_pubkey: Script, price: Price) -> Result<Self> {
// Falls back to Amount::ZERO in case we don't find an output that matches out script pubkey
// The assumption is, that this can happen for cases where we were liuqidated
// The assumption is, that this can happen for cases where we were liquidated
let payout = match tx
.output
.iter()

110
daemon/tests/happy_path.rs

@ -2,9 +2,11 @@ use std::time::Duration;
use crate::harness::flow::{is_next_none, next, next_cfd, next_order, next_some};
use crate::harness::{
dummy_new_order, init_tracing, start_both, Maker, MakerConfig, Taker, TakerConfig,
deliver_close_finality_event, deliver_lock_finality_event, dummy_new_order, init_tracing,
start_both, Maker, MakerConfig, Taker, TakerConfig,
};
use daemon::connection::ConnectionStatus;
use daemon::model::cfd::OrderId;
use daemon::model::Usd;
use daemon::projection::{CfdState, Identity};
use maia::secp256k1_zkp::schnorrsig;
@ -116,6 +118,47 @@ async fn taker_takes_order_and_maker_accepts_and_contract_setup() {
assert_eq!(maker_cfd.order_id, received.id);
assert!(matches!(taker_cfd.state, CfdState::PendingOpen { .. }));
assert!(matches!(maker_cfd.state, CfdState::PendingOpen { .. }));
deliver_lock_finality_event(&maker, &taker, received.id).await;
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::Open { .. }));
assert!(matches!(maker_cfd.state, CfdState::Open { .. }));
}
#[tokio::test]
async fn collaboratively_close_an_open_cfd() {
let _guard = init_tracing();
let (mut maker, mut taker, order_id) = start_from_open_cfd_state().await;
taker.propose_settlement(order_id).await;
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(
taker_cfd.state,
CfdState::OutgoingSettlementProposal { .. }
));
assert!(matches!(
maker_cfd.state,
CfdState::IncomingSettlementProposal { .. }
));
maker.mocks.mock_monitor_collaborative_settlement().await;
taker.mocks.mock_monitor_collaborative_settlement().await;
maker.accept_settlement_proposal(order_id).await;
sleep(Duration::from_secs(5)).await; // need to wait a bit until both transition
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::PendingClose { .. }));
assert!(matches!(maker_cfd.state, CfdState::PendingClose { .. }));
deliver_close_finality_event(&maker, &taker, order_id).await;
sleep(Duration::from_secs(5)).await; // need to wait a bit until both transition
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::Closed { .. }));
assert!(matches!(maker_cfd.state, CfdState::Closed { .. }));
}
#[tokio::test]
@ -179,3 +222,68 @@ async fn maker_notices_lack_of_taker() {
next(maker.connected_takers_feed()).await.unwrap()
);
}
/// Hide the implementation detail of arriving at the Cfd open state.
/// Useful when reading tests that should start at this point.
/// For convenience, returns also OrderId of the opened Cfd.
async fn start_from_open_cfd_state() -> (Maker, Taker, OrderId) {
let heartbeat_interval = Duration::from_secs(60);
let maker_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let mut maker = Maker::start(
&MakerConfig::default().with_heartbeat_interval(heartbeat_interval),
maker_listener,
)
.await;
let mut taker = Taker::start(
&TakerConfig::default().with_heartbeat_timeout(heartbeat_interval * 2),
maker.listen_addr,
maker.identity_pk,
)
.await;
is_next_none(taker.order_feed()).await.unwrap();
maker.publish_order(dummy_new_order()).await;
let (_, received) = next_order(maker.order_feed(), taker.order_feed())
.await
.unwrap();
taker.mocks.mock_oracle_announcement().await;
maker.mocks.mock_oracle_announcement().await;
taker.take_order(received.clone(), Usd::new(dec!(5))).await;
let (_, _) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
maker.mocks.mock_party_params().await;
taker.mocks.mock_party_params().await;
maker.mocks.mock_monitor_oracle_attestation().await;
taker.mocks.mock_monitor_oracle_attestation().await;
maker.mocks.mock_oracle_monitor_attestation().await;
taker.mocks.mock_oracle_monitor_attestation().await;
maker.mocks.mock_monitor_start_monitoring().await;
taker.mocks.mock_monitor_start_monitoring().await;
maker.accept_take_request(received.clone()).await;
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::ContractSetup { .. }));
assert!(matches!(maker_cfd.state, CfdState::ContractSetup { .. }));
maker.mocks.mock_wallet_sign_and_broadcast().await;
taker.mocks.mock_wallet_sign_and_broadcast().await;
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::PendingOpen { .. }));
assert!(matches!(maker_cfd.state, CfdState::PendingOpen { .. }));
deliver_lock_finality_event(&maker, &taker, received.id).await;
let (taker_cfd, maker_cfd) = next_cfd(taker.cfd_feed(), maker.cfd_feed()).await.unwrap();
assert!(matches!(taker_cfd.state, CfdState::Open { .. }));
assert!(matches!(maker_cfd.state, CfdState::Open { .. }));
(maker, taker, received.id)
}

29
daemon/tests/harness/bdk.rs

@ -1,29 +0,0 @@
use bdk::bitcoin::util::psbt::{Global, PartiallySignedTransaction};
use bdk::bitcoin::{Transaction, Txid};
use std::collections::BTreeMap;
pub fn dummy_partially_signed_transaction() -> PartiallySignedTransaction {
// very simple dummy psbt that does not contain anything
// pulled in from github.com-1ecc6299db9ec823/bitcoin-0.27.1/src/util/psbt/mod.rs:238
PartiallySignedTransaction {
global: Global {
unsigned_tx: Transaction {
version: 2,
lock_time: 0,
input: vec![],
output: vec![],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
},
inputs: vec![],
outputs: vec![],
}
}
pub fn dummy_tx_id() -> Txid {
dummy_partially_signed_transaction().extract_tx().txid()
}

18
daemon/tests/harness/mocks/mod.rs

@ -6,8 +6,6 @@ use self::monitor::MonitorActor;
use self::oracle::OracleActor;
use self::wallet::WalletActor;
use super::bdk::{dummy_partially_signed_transaction, dummy_tx_id};
pub mod monitor;
pub mod oracle;
pub mod wallet;
@ -39,19 +37,14 @@ impl Mocks {
// Helper function setting up a "happy path" wallet mock
pub async fn mock_wallet_sign_and_broadcast(&mut self) {
let mut seq = mockall::Sequence::new();
self.wallet()
.await
.expect_sign()
.times(1)
.returning(|_| Ok(dummy_partially_signed_transaction()))
.in_sequence(&mut seq);
.returning(|sign_msg| Ok(sign_msg.psbt));
self.wallet()
.await
.expect_broadcast()
.times(1)
.returning(|_| Ok(dummy_tx_id()))
.in_sequence(&mut seq);
.returning(|broadcast_msg| Ok(broadcast_msg.tx.txid()));
}
pub async fn mock_oracle_announcement(&mut self) {
@ -89,6 +82,13 @@ impl Mocks {
.expect_start_monitoring()
.return_const(());
}
pub async fn mock_monitor_collaborative_settlement(&mut self) {
self.monitor()
.await
.expect_collaborative_settlement()
.return_const(());
}
}
impl Default for Mocks {

86
daemon/tests/harness/mod.rs

@ -5,7 +5,7 @@ use crate::schnorrsig;
use ::bdk::bitcoin::Network;
use daemon::bitmex_price_feed::Quote;
use daemon::connection::{connect, ConnectionStatus};
use daemon::model::cfd::Role;
use daemon::model::cfd::{OrderId, Role};
use daemon::model::{self, Price, Timestamp, Usd};
use daemon::projection::{Cfd, CfdOrder, Feeds, Identity};
use daemon::seed::Seed;
@ -27,7 +27,6 @@ use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use xtra::Actor;
pub mod bdk;
pub mod flow;
pub mod maia;
pub mod mocks;
@ -174,14 +173,8 @@ impl Maker {
.await
.unwrap();
let dummy_quote = Quote {
timestamp: Timestamp::now(),
bid: Price::new(dec!(10000)).unwrap(),
ask: Price::new(dec!(10000)).unwrap(),
};
let (proj_actor, feeds) =
projection::Actor::new(Role::Maker, Network::Testnet, vec![], dummy_quote);
projection::Actor::new(Role::Maker, Network::Testnet, vec![], dummy_quote());
tasks.add(projection_context.run(proj_actor));
let address = listener.local_addr().unwrap();
@ -237,6 +230,15 @@ impl Maker {
.unwrap()
.unwrap();
}
pub async fn accept_settlement_proposal(&self, order_id: OrderId) {
self.system
.cfd_actor_addr
.send(maker_cfd::AcceptSettlement { order_id })
.await
.unwrap()
.unwrap();
}
}
/// Taker Test Setup
@ -297,14 +299,8 @@ impl Taker {
.await
.unwrap();
let dummy_quote = Quote {
timestamp: Timestamp::now(),
bid: Price::new(dec!(10000)).unwrap(),
ask: Price::new(dec!(10000)).unwrap(),
};
let (proj_actor, feeds) =
projection::Actor::new(Role::Taker, Network::Testnet, vec![], dummy_quote);
projection::Actor::new(Role::Taker, Network::Testnet, vec![], dummy_quote());
tasks.add(projection_context.run(proj_actor));
tasks.add(connect(
@ -334,6 +330,50 @@ impl Taker {
.unwrap()
.unwrap();
}
pub async fn propose_settlement(&self, order_id: OrderId) {
self.system
.cfd_actor_addr
.send(taker_cfd::ProposeSettlement {
order_id,
current_price: dummy_price(),
})
.await
.unwrap()
.unwrap();
}
}
/// Deliver the event that provokes the transition to cfd's "Open" state
pub async fn deliver_lock_finality_event(maker: &Maker, taker: &Taker, id: OrderId) {
maker
.system
.cfd_actor_addr
.send(daemon::monitor::Event::LockFinality(id))
.await
.unwrap();
taker
.system
.cfd_actor_addr
.send(daemon::monitor::Event::LockFinality(id))
.await
.unwrap();
}
/// Deliver the event that provokes the transition to cfd's "Close" state
pub async fn deliver_close_finality_event(maker: &Maker, taker: &Taker, id: OrderId) {
taker
.system
.cfd_actor_addr
.send(daemon::monitor::Event::CloseFinality(id))
.await
.unwrap();
maker
.system
.cfd_actor_addr
.send(daemon::monitor::Event::CloseFinality(id))
.await
.unwrap();
}
async fn in_memory_db() -> SqlitePool {
@ -347,9 +387,21 @@ async fn in_memory_db() -> SqlitePool {
pool
}
pub fn dummy_price() -> Price {
Price::new(dec!(50_000)).expect("to not fail")
}
pub fn dummy_quote() -> Quote {
Quote {
timestamp: Timestamp::now(),
bid: dummy_price(),
ask: dummy_price(),
}
}
pub fn dummy_new_order() -> maker_cfd::NewOrder {
maker_cfd::NewOrder {
price: Price::new(dec!(50_000)).expect("unexpected failure"),
price: dummy_price(),
min_quantity: Usd::new(dec!(5)),
max_quantity: Usd::new(dec!(100)),
}

Loading…
Cancel
Save