Browse Source

Merge #779

779: Hello message r=da-kami a=da-kami



Co-authored-by: Daniel Karzel <daniel@comit.network>
feature/reconnect-button
bors[bot] 3 years ago
committed by GitHub
parent
commit
883b47698d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      Cargo.lock
  2. 1
      daemon/Cargo.toml
  3. 103
      daemon/src/connection.rs
  4. 2
      daemon/src/lib.rs
  5. 4
      daemon/src/maker_cfd.rs
  6. 53
      daemon/src/maker_inc_connections.rs
  7. 4
      daemon/src/model.rs
  8. 8
      daemon/src/send_to_socket.rs
  9. 4
      daemon/src/taker.rs
  10. 3
      daemon/src/taker_cfd.rs
  11. 39
      daemon/src/to_sse_event.rs
  12. 19
      daemon/src/wire.rs
  13. 6
      daemon/tests/happy_path.rs
  14. 15
      daemon/tests/harness/mod.rs
  15. 7
      taker-frontend/src/App.tsx
  16. 8
      taker-frontend/src/components/History.tsx
  17. 18
      taker-frontend/src/components/NavBar.tsx
  18. 6
      taker-frontend/src/components/Trade.tsx
  19. 10
      taker-frontend/src/types.ts

4
Cargo.lock

@ -631,6 +631,7 @@ dependencies = [
"rust-embed",
"rust_decimal",
"rust_decimal_macros",
"semver 1.0.4",
"serde",
"serde_json",
"serde_plain",
@ -2702,6 +2703,9 @@ name = "semver"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
dependencies = [
"serde",
]
[[package]]
name = "semver-parser"

1
daemon/Cargo.toml

@ -29,6 +29,7 @@ rocket-basicauth = { version = "2", default-features = false }
rust-embed = "6.3"
rust_decimal = "1.18"
rust_decimal_macros = "1.18"
semver = { version = "1.0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_plain = "1"

103
daemon/src/connection.rs

@ -1,18 +1,19 @@
use crate::address_map::{AddressMap, Stopping};
use crate::model::cfd::OrderId;
use crate::model::{Price, Timestamp, Usd};
use crate::model::{Identity, Price, Timestamp, Usd};
use crate::tokio_ext::FutureExt;
use crate::wire::{EncryptedJsonCodec, TakerToMaker, Version};
use crate::{collab_settlement_taker, log_error, noise, send_to_socket, setup_taker, wire, Tasks};
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use bdk::bitcoin::Amount;
use futures::StreamExt;
use futures::{SinkExt, StreamExt, TryStreamExt};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use tokio::net::TcpStream;
use tokio::sync::watch;
use tokio_util::codec::FramedRead;
use tokio_util::codec::{FramedRead, FramedWrite};
use xtra::prelude::MessageChannel;
use xtra::KeepRunning;
use xtra_productivity::xtra_productivity;
@ -40,7 +41,7 @@ pub struct Actor {
}
pub struct Connect {
pub maker_identity_pk: x25519_dalek::PublicKey,
pub maker_identity: Identity,
pub maker_addr: SocketAddr,
}
@ -54,7 +55,17 @@ struct MeasurePulse;
#[derive(Clone, Debug, PartialEq)]
pub enum ConnectionStatus {
Online,
Offline,
Offline {
reason: Option<ConnectionCloseReason>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum ConnectionCloseReason {
VersionMismatch {
taker_version: Version,
maker_version: Version,
},
}
/// Message sent from the `setup_taker::Actor` to the
@ -167,7 +178,7 @@ impl Actor {
&mut self,
Connect {
maker_addr,
maker_identity_pk,
maker_identity,
}: Connect,
ctx: &mut xtra::Context<Self>,
) -> Result<()> {
@ -185,25 +196,69 @@ impl Actor {
)
})?
.with_context(|| format!("Failed to connect to {}", maker_addr))?;
let noise =
noise::initiator_handshake(&mut connection, &self.identity_sk, &maker_identity_pk)
.await?;
let noise = noise::initiator_handshake(
&mut connection,
&self.identity_sk,
&maker_identity.pk(),
)
.await?;
let (read, write) = connection.into_split();
(read, write, Arc::new(Mutex::new(noise)))
};
let send_to_socket = send_to_socket::Actor::new(write, noise.clone());
let mut read = FramedRead::new(read, wire::EncryptedJsonCodec::new(noise.clone()));
let mut write = FramedWrite::new(write, EncryptedJsonCodec::new(noise));
let mut tasks = Tasks::default();
tasks.add(self.send_to_maker_ctx.attach(send_to_socket));
let our_version = Version::current();
write.send(TakerToMaker::Hello(our_version.clone())).await?;
let read = FramedRead::new(read, wire::EncryptedJsonCodec::new(noise))
.map(move |item| MakerStreamMessage { item });
match read
.try_next()
.timeout(Duration::from_secs(10))
.await
.with_context(|| {
format!(
"Maker {} did not send Hello within 10 seconds, dropping connection",
maker_identity
)
})? {
Ok(Some(wire::MakerToTaker::Hello(maker_version))) => {
if our_version != maker_version {
self.status_sender
.send(ConnectionStatus::Offline {
reason: Some(ConnectionCloseReason::VersionMismatch {
taker_version: our_version.clone(),
maker_version: maker_version.clone(),
}),
})
.expect("receiver to outlive the actor");
bail!(
"Network version mismatch, we are on version {} but taker is on version {}",
our_version,
maker_version,
)
}
}
unexpected_message => {
bail!(
"Unexpected message {:?} from maker {}",
unexpected_message,
maker_identity
)
}
}
tracing::info!(address = %maker_addr, "Established connection to maker");
let this = ctx.address().expect("self to be alive");
tasks.add(this.attach_stream(read));
let send_to_socket = send_to_socket::Actor::new(write);
let mut tasks = Tasks::default();
tasks.add(self.send_to_maker_ctx.attach(send_to_socket));
tasks.add(this.attach_stream(read.map(move |item| MakerStreamMessage { item })));
tasks.add(
ctx.notify_interval(self.heartbeat_timeout, || MeasurePulse)
.expect("we just started"),
@ -217,8 +272,6 @@ impl Actor {
.send(ConnectionStatus::Online)
.expect("receiver to outlive the actor");
tracing::info!(address = %maker_addr, "Established connection to maker");
Ok(())
}
@ -285,6 +338,9 @@ impl Actor {
tracing::warn!(%order_id, "No active collaborative settlement");
}
}
wire::MakerToTaker::Hello(_) => {
tracing::warn!("Ignoring unexpected Hello message from maker. Hello is only expected when opening a new connection.")
}
other => {
// this one should go to the taker cfd actor
log_error!(self.maker_to_taker.send(other));
@ -305,7 +361,7 @@ impl Actor {
if time_since_last_heartbeat > self.heartbeat_timeout {
self.status_sender
.send(ConnectionStatus::Offline)
.send(ConnectionStatus::Offline { reason: None })
.expect("watch receiver to outlive the actor");
self.connected_state = None;
}
@ -319,16 +375,17 @@ impl xtra::Actor for Actor {}
pub async fn connect(
mut maker_online_status_feed_receiver: watch::Receiver<ConnectionStatus>,
connection_actor_addr: xtra::Address<Actor>,
maker_identity_pk: x25519_dalek::PublicKey,
maker_identity: Identity,
maker_addresses: Vec<SocketAddr>,
) {
loop {
if maker_online_status_feed_receiver.borrow().clone() == ConnectionStatus::Offline {
let connection_status = maker_online_status_feed_receiver.borrow().clone();
if matches!(connection_status, ConnectionStatus::Offline { .. }) {
tracing::debug!("No connection to the maker");
'connect: loop {
for address in &maker_addresses {
let connect_msg = Connect {
maker_identity_pk,
maker_identity,
maker_addr: *address,
};
@ -337,7 +394,7 @@ pub async fn connect(
.await
.expect("Taker actor to be present")
{
tracing::trace!(%address, "Failed to establish connection: {:#}", e);
tracing::warn!(%address, "Failed to establish connection: {:#}", e);
continue;
}
break 'connect;

2
daemon/src/lib.rs

@ -245,7 +245,7 @@ where
let cfds = load_all_cfds(&mut conn).await?;
let (maker_online_status_feed_sender, maker_online_status_feed_receiver) =
watch::channel(ConnectionStatus::Offline);
watch::channel(ConnectionStatus::Offline { reason: None });
let (monitor_addr, mut monitor_ctx) = xtra::Context::new(None);
let (oracle_addr, mut oracle_ctx) = xtra::Context::new(None);

4
daemon/src/maker_cfd.rs

@ -13,6 +13,7 @@ use crate::projection::{
};
use crate::setup_contract::RolloverParams;
use crate::tokio_ext::FutureExt;
use crate::wire::TakerToMaker;
use crate::{
log_error, maker_inc_connections, monitor, oracle, projection, setup_contract, setup_maker,
wallet, wire, Tasks,
@ -1109,6 +1110,9 @@ where
wire::TakerToMaker::Protocol { .. } => {
unreachable!("This kind of message should be sent to the `setup_maker::Actor`")
}
TakerToMaker::Hello(_) => {
unreachable!("The Hello message is not sent to the cfd actor")
}
}
}
}

53
daemon/src/maker_inc_connections.rs

@ -3,16 +3,18 @@ use crate::maker_cfd::{FromTaker, TakerConnected, TakerDisconnected};
use crate::model::cfd::{Order, OrderId};
use crate::model::Identity;
use crate::noise::TransportStateExt;
use crate::tokio_ext::FutureExt;
use crate::wire::{EncryptedJsonCodec, MakerToTaker, TakerToMaker, Version};
use crate::{maker_cfd, noise, send_to_socket, setup_maker, wire, Tasks};
use anyhow::Result;
use futures::TryStreamExt;
use anyhow::{bail, Context, Result};
use futures::{SinkExt, TryStreamExt};
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio_util::codec::FramedRead;
use tokio_util::codec::{FramedRead, FramedWrite};
use xtra::prelude::*;
use xtra::KeepRunning;
use xtra_productivity::xtra_productivity;
@ -116,18 +118,53 @@ impl Actor {
&mut self,
mut stream: TcpStream,
taker_address: SocketAddr,
ctx: &mut Context<Self>,
ctx: &mut xtra::Context<Self>,
) -> Result<()> {
let transport_state = noise::responder_handshake(&mut stream, &self.noise_priv_key).await?;
let taker_id = Identity::new(transport_state.get_remote_public_key()?);
tracing::info!(%taker_id, address = %taker_address, "New taker connected");
let transport_state = Arc::new(Mutex::new(transport_state));
let (read, write) = stream.into_split();
let mut read =
FramedRead::new(read, wire::EncryptedJsonCodec::new(transport_state.clone()));
let mut write = FramedWrite::new(write, EncryptedJsonCodec::new(transport_state));
match read
.try_next()
.timeout(Duration::from_secs(10))
.await
.with_context(|| {
format!(
"Taker {} did not send Hello within 10 seconds, dropping connection",
taker_id
)
})? {
Ok(Some(TakerToMaker::Hello(taker_version))) => {
let our_version = Version::current();
write.send(MakerToTaker::Hello(our_version.clone())).await?;
if our_version != taker_version {
tracing::debug!(
"Network version mismatch, we are on version {} but taker is on version {}",
our_version,
taker_version
);
// A taker running a different version is not treated as error for the maker
return Ok(());
}
}
unexpected_message => {
bail!(
"Unexpected message {:?} from taker {}",
unexpected_message,
taker_id
);
}
}
tracing::info!(%taker_id, address = %taker_address, "New taker connected");
let this = ctx.address().expect("self to be alive");
let read_fut = async move {
@ -143,7 +180,7 @@ impl Actor {
};
let (out_msg, mut out_msg_actor_context) = xtra::Context::new(None);
let send_to_socket_actor = send_to_socket::Actor::new(write, transport_state.clone());
let send_to_socket_actor = send_to_socket::Actor::new(write);
let heartbeat_fut = out_msg_actor_context
.notify_interval(self.heartbeat_interval, || wire::MakerToTaker::Heartbeat)
@ -200,7 +237,7 @@ impl Actor {
Ok(())
}
async fn handle(&mut self, msg: ListenerMessage, ctx: &mut Context<Self>) -> KeepRunning {
async fn handle(&mut self, msg: ListenerMessage, ctx: &mut xtra::Context<Self>) -> KeepRunning {
match msg {
ListenerMessage::NewConnection { stream, address } => {
if let Err(err) = self.handle_new_connection_impl(stream, address, ctx).await {

4
daemon/src/model.rs

@ -395,6 +395,10 @@ impl Identity {
pub fn new(key: x25519_dalek::PublicKey) -> Self {
Self(key)
}
pub fn pk(&self) -> x25519_dalek::PublicKey {
self.0
}
}
impl Serialize for Identity {

8
daemon/src/send_to_socket.rs

@ -1,9 +1,7 @@
use crate::wire::{self, EncryptedJsonCodec};
use futures::SinkExt;
use serde::Serialize;
use snow::TransportState;
use std::fmt;
use std::sync::{Arc, Mutex};
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::OwnedWriteHalf;
use tokio_util::codec::FramedWrite;
@ -14,10 +12,8 @@ pub struct Actor<T> {
}
impl<T> Actor<T> {
pub fn new(write: OwnedWriteHalf, transport_state: Arc<Mutex<TransportState>>) -> Self {
Self {
write: FramedWrite::new(write, EncryptedJsonCodec::new(transport_state)),
}
pub fn new(write: FramedWrite<OwnedWriteHalf, EncryptedJsonCodec<T>>) -> Self {
Self { write }
}
pub async fn shutdown(self) {

4
daemon/src/taker.rs

@ -6,7 +6,7 @@ use clap::{Parser, Subcommand};
use daemon::connection::connect;
use daemon::db::load_all_cfds;
use daemon::model::cfd::Role;
use daemon::model::WalletInfo;
use daemon::model::{Identity, WalletInfo};
use daemon::seed::Seed;
use daemon::tokio_ext::FutureExt;
use daemon::{
@ -274,7 +274,7 @@ async fn main() -> Result<()> {
tasks.add(connect(
maker_online_status_feed_receiver.clone(),
connection_actor_addr,
opts.maker_id,
Identity::new(opts.maker_id),
possible_addresses,
));

3
daemon/src/taker_cfd.rs

@ -663,6 +663,9 @@ where
wire::MakerToTaker::Settlement { .. } => {
unreachable!("These messages should be sent to the `collab_settlement::Actor`")
}
wire::MakerToTaker::Hello(_) => {
unreachable!("Connection related messages are handled in the connection actor")
}
}
}
}

39
daemon/src/to_sse_event.rs

@ -1,7 +1,7 @@
use crate::connection::ConnectionStatus;
use crate::model;
use crate::model::Timestamp;
use crate::projection::{Cfd, CfdAction, CfdOrder, Identity, Quote};
use crate::to_sse_event::ConnectionCloseReason::{MakerVersionOutdated, TakerVersionOutdated};
use crate::{connection, model};
use bdk::bitcoin::Amount;
use rocket::request::FromParam;
use rocket::response::stream::Event;
@ -58,11 +58,40 @@ impl ToSseEvent for model::WalletInfo {
}
}
impl ToSseEvent for ConnectionStatus {
#[derive(Debug, Clone, Serialize)]
pub struct ConnectionStatus {
online: bool,
connection_close_reason: Option<ConnectionCloseReason>,
}
#[derive(Debug, Clone, Serialize)]
pub enum ConnectionCloseReason {
MakerVersionOutdated,
TakerVersionOutdated,
}
impl ToSseEvent for connection::ConnectionStatus {
fn to_sse_event(&self) -> Event {
let connected = match self {
ConnectionStatus::Online => true,
ConnectionStatus::Offline => false,
connection::ConnectionStatus::Online => ConnectionStatus {
online: true,
connection_close_reason: None,
},
connection::ConnectionStatus::Offline { reason } => ConnectionStatus {
online: false,
connection_close_reason: reason.as_ref().map(|g| match g {
connection::ConnectionCloseReason::VersionMismatch {
maker_version,
taker_version,
} => {
if *maker_version < *taker_version {
MakerVersionOutdated
} else {
TakerVersionOutdated
}
}
}),
},
};
Event::json(&connected).event("maker_status")

19
daemon/src/wire.rs

@ -18,6 +18,21 @@ use std::ops::RangeInclusive;
use std::sync::{Arc, Mutex};
use tokio_util::codec::{Decoder, Encoder, LengthDelimitedCodec};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd)]
pub struct Version(semver::Version);
impl Version {
pub fn current() -> Self {
Self(semver::Version::new(1, 0, 0))
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
pub mod taker_to_maker {
use super::*;
@ -43,6 +58,7 @@ pub mod taker_to_maker {
#[serde(tag = "type", content = "payload")]
#[allow(clippy::large_enum_variant)]
pub enum TakerToMaker {
Hello(Version),
TakeOrder {
order_id: OrderId,
quantity: Usd,
@ -70,6 +86,7 @@ impl fmt::Display for TakerToMaker {
TakerToMaker::ProposeRollOver { .. } => write!(f, "ProposeRollOver"),
TakerToMaker::RollOverProtocol(_) => write!(f, "RollOverProtocol"),
TakerToMaker::Settlement { .. } => write!(f, "Settlement"),
TakerToMaker::Hello(_) => write!(f, "Hello"),
}
}
}
@ -78,6 +95,7 @@ impl fmt::Display for TakerToMaker {
#[serde(tag = "type", content = "payload")]
#[allow(clippy::large_enum_variant)]
pub enum MakerToTaker {
Hello(Version),
/// Periodically broadcasted message, indicating maker's presence
Heartbeat,
CurrentOrder(Option<Order>),
@ -114,6 +132,7 @@ pub mod maker_to_taker {
impl fmt::Display for MakerToTaker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MakerToTaker::Hello(_) => write!(f, "Hello"),
MakerToTaker::Heartbeat { .. } => write!(f, "Heartbeat"),
MakerToTaker::CurrentOrder(_) => write!(f, "CurrentOrder"),
MakerToTaker::ConfirmOrder(_) => write!(f, "ConfirmOrder"),

6
daemon/tests/happy_path.rs

@ -177,7 +177,7 @@ async fn taker_notices_lack_of_maker() {
let taker_config = TakerConfig::default().with_heartbeat_timeout(short_interval * 2);
let mut taker = Taker::start(&taker_config, maker.listen_addr, maker.identity_pk).await;
let mut taker = Taker::start(&taker_config, maker.listen_addr, maker.identity).await;
assert_eq!(
ConnectionStatus::Online,
@ -189,7 +189,7 @@ async fn taker_notices_lack_of_maker() {
sleep(taker_config.heartbeat_timeout).await;
assert_eq!(
ConnectionStatus::Offline,
ConnectionStatus::Offline { reason: None },
next(taker.maker_status_feed()).await.unwrap(),
);
@ -237,7 +237,7 @@ async fn start_from_open_cfd_state() -> (Maker, Taker, OrderId) {
let mut taker = Taker::start(
&TakerConfig::default().with_heartbeat_timeout(heartbeat_interval * 2),
maker.listen_addr,
maker.identity_pk,
maker.identity,
)
.await;

15
daemon/tests/harness/mod.rs

@ -44,12 +44,7 @@ fn oracle_pk() -> schnorrsig::PublicKey {
pub async fn start_both() -> (Maker, Taker) {
let maker_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let maker = Maker::start(&MakerConfig::default(), maker_listener).await;
let taker = Taker::start(
&TakerConfig::default(),
maker.listen_addr,
maker.identity_pk,
)
.await;
let taker = Taker::start(&TakerConfig::default(), maker.listen_addr, maker.identity).await;
(maker, taker)
}
@ -115,7 +110,7 @@ pub struct Maker {
pub mocks: mocks::Mocks,
pub feeds: Feeds,
pub listen_addr: SocketAddr,
pub identity_pk: x25519_dalek::PublicKey,
pub identity: model::Identity,
_tasks: Tasks,
}
@ -195,7 +190,7 @@ impl Maker {
Self {
system: maker,
feeds,
identity_pk,
identity: model::Identity::new(identity_pk),
listen_addr: address,
mocks,
_tasks: tasks,
@ -266,7 +261,7 @@ impl Taker {
pub async fn start(
config: &TakerConfig,
maker_address: SocketAddr,
maker_noise_pub_key: x25519_dalek::PublicKey,
maker_identity: model::Identity,
) -> Self {
let (identity_pk, identity_sk) = config.seed.derive_identity();
@ -306,7 +301,7 @@ impl Taker {
tasks.add(connect(
taker.maker_online_status_feed_receiver.clone(),
taker.connection_actor_addr.clone(),
maker_noise_pub_key,
maker_identity,
vec![maker_address],
));

7
taker-frontend/src/App.tsx

@ -25,6 +25,7 @@ import {
BXBTData,
Cfd,
CfdOrderRequestPayload,
ConnectionStatus,
intoCfd,
intoOrder,
MarginRequestPayload,
@ -63,8 +64,8 @@ export const App = () => {
const cfdsOrUndefined = useLatestEvent<Cfd[]>(source, "cfds", intoCfd);
let cfds = cfdsOrUndefined ? cfdsOrUndefined! : [];
cfds.sort((a, b) => a.order_id.localeCompare(b.order_id));
const connectedToMakerOrUndefined = useLatestEvent<boolean>(source, "maker_status");
const connectedToMaker = connectedToMakerOrUndefined ? connectedToMakerOrUndefined! : false;
const connectedToMakerOrUndefined = useLatestEvent<ConnectionStatus>(source, "maker_status");
const connectedToMaker = connectedToMakerOrUndefined ? connectedToMakerOrUndefined : { online: false };
let [quantity, setQuantity] = useState("0");
let [margin, setMargin] = useState(0);
@ -154,7 +155,7 @@ export const App = () => {
cfds={cfds.filter((cfd) =>
cfd.state.getGroup() === StateGroupKey.CLOSED
)}
connectedToMaker
connectedToMaker={connectedToMaker}
/>
</AccordionPanel>
</AccordionItem>

8
taker-frontend/src/components/History.tsx

@ -18,14 +18,14 @@ import {
VStack,
} from "@chakra-ui/react";
import * as React from "react";
import { Cfd, Tx, TxLabel } from "../types";
import { Cfd, ConnectionStatus, Tx, TxLabel } from "../types";
import usePostRequest from "../usePostRequest";
import CloseButton from "./CloseButton";
interface HistoryProps {
cfds: Cfd[];
title?: string;
connectedToMaker: Boolean;
connectedToMaker: ConnectionStatus;
}
const History = ({ cfds, title, connectedToMaker }: HistoryProps) => {
@ -51,7 +51,7 @@ export default History;
interface CfdDetailsProps {
cfd: Cfd;
connectedToMaker: Boolean;
connectedToMaker: ConnectionStatus;
}
const CfdDetails = ({ cfd, connectedToMaker }: CfdDetailsProps) => {
@ -74,7 +74,7 @@ const CfdDetails = ({ cfd, connectedToMaker }: CfdDetailsProps) => {
let [settle, isSettling] = usePostRequest(`/api/cfd/${cfd.order_id}/settle`);
let [commit, isCommiting] = usePostRequest(`/api/cfd/${cfd.order_id}/commit`);
const closeButton = connectedToMaker
const closeButton = connectedToMaker.online
? <CloseButton request={settle} status={isSettling} cfd={cfd} action="Close" />
: <CloseButton request={commit} status={isCommiting} cfd={cfd} action="Force Close" />;

18
taker-frontend/src/components/NavBar.tsx

@ -17,11 +17,11 @@ import * as React from "react";
import { useNavigate } from "react-router-dom";
import logoBlack from "../images/logo_nav_bar_black.svg";
import logoWhite from "../images/logo_nav_bar_white.svg";
import { WalletInfo } from "../types";
import { ConnectionCloseReason, ConnectionStatus, WalletInfo } from "../types";
interface NavProps {
walletInfo: WalletInfo | null;
connectedToMaker: boolean;
connectedToMaker: ConnectionStatus;
}
export default function Nav({ walletInfo, connectedToMaker }: NavProps) {
@ -38,6 +38,18 @@ export default function Nav({ walletInfo, connectedToMaker }: NavProps) {
<SunIcon />,
);
let connectionMessage = connectedToMaker.online ? "Online" : "Offline";
if (connectedToMaker.connection_close_reason) {
switch (connectedToMaker.connection_close_reason) {
case ConnectionCloseReason.MAKER_VERSION_OUTDATED:
connectionMessage = connectionMessage + ": the maker is running an outdated version";
break;
case ConnectionCloseReason.TAKER_VERSION_OUTDATED:
connectionMessage = connectionMessage + " - you are running an incompatible version, please upgrade!";
break;
}
}
return (
<>
<Box bg={useColorModeValue("gray.100", "gray.900")} px={4}>
@ -56,7 +68,7 @@ export default function Nav({ walletInfo, connectedToMaker }: NavProps) {
<MenuItem onClick={() => navigate("/wallet")}>Wallet</MenuItem>
</MenuList>
</Menu>
<Heading size={"sm"}>{"Maker status: " + (connectedToMaker ? "Online" : "Offline")}</Heading>
<Heading size={"sm"}>{"Maker status: " + connectionMessage}</Heading>
<Flex alignItems={"center"}>
<Stack direction={"row"} spacing={7}>
<Button onClick={toggleColorMode} bg={"transparent"}>

6
taker-frontend/src/components/Trade.tsx

@ -47,12 +47,12 @@ import {
} from "@chakra-ui/react";
import { motion } from "framer-motion";
import * as React from "react";
import { CfdOrderRequestPayload } from "../types";
import { CfdOrderRequestPayload, ConnectionStatus } from "../types";
const MotionBox = motion<BoxProps>(Box);
interface TradeProps {
connectedToMaker: boolean;
connectedToMaker: ConnectionStatus;
orderId?: string;
minQuantity: number;
maxQuantity: number;
@ -120,7 +120,7 @@ export default function Trade({
let alertBox;
if (!connectedToMaker) {
if (!connectedToMaker.online) {
alertBox = <AlertBox
title={"No maker!"}
description={"You are not connected to any maker. Functionality may be limited"}

10
taker-frontend/src/types.ts

@ -277,3 +277,13 @@ export interface WithdrawRequest {
amount?: number;
fee: number;
}
export interface ConnectionStatus {
online: boolean;
connection_close_reason?: ConnectionCloseReason;
}
export const enum ConnectionCloseReason {
MAKER_VERSION_OUTDATED = "MakerVersionOutdated",
TAKER_VERSION_OUTDATED = "TakerVersionOutdated",
}

Loading…
Cancel
Save