Improve error reporting of failed protocols

Instead of forwarding every error, we deliberately ignore certain
variants that are not worth being printed to the log. In particular,
this concerns "UnsupportedProtocols" and "ResponseOmission".

To make this less verbose we introduce a macro for mapping a
`RequestResponseEvent` to `{alice,bob}::OutEvent`. We use a macro
because those `OutEvent`s are different types and the only other
way of abstracting over them would be to introduce traits that we
implement on both of them.

To make the macro easier to use, we move all the `From` implementations
that convert between the protocol and the more high-level behaviour
into the actual protocol module.
pull/408/head
Thomas Eizinger 3 years ago
parent f0f7288bb6
commit b417950f99
No known key found for this signature in database
GPG Key ID: 651AC83A6C6C8B96

@ -1,3 +1,5 @@
mod impl_from_rr_event;
pub mod cbor_request_response;
pub mod encrypted_signature;
pub mod quote;

@ -1,20 +1,26 @@
use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob};
use libp2p::core::ProtocolName;
use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage,
};
use libp2p::PeerId;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub type OutEvent = RequestResponseEvent<Request, ()>;
const PROTOCOL: &str = "/comit/xmr/btc/encrypted_signature/1.0.0";
type OutEvent = RequestResponseEvent<Request, ()>;
type Message = RequestResponseMessage<Request, ()>;
pub type Behaviour = RequestResponse<CborCodec<EncryptedSignatureProtocol, Request, ()>>;
#[derive(Debug, Clone, Copy, Default)]
pub struct EncryptedSignatureProtocol;
impl ProtocolName for EncryptedSignatureProtocol {
fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/encrypted_signature/1.0.0"
PROTOCOL.as_bytes()
}
}
@ -24,10 +30,6 @@ pub struct Request {
pub tx_redeem_encsig: crate::bitcoin::EncryptedSignature,
}
pub type Behaviour = RequestResponse<CborCodec<EncryptedSignatureProtocol, Request, ()>>;
pub type Message = RequestResponseMessage<Request, ()>;
pub fn alice() -> Behaviour {
Behaviour::new(
CborCodec::default(),
@ -43,3 +45,31 @@ pub fn bob() -> Behaviour {
RequestResponseConfig::default(),
)
}
impl From<(PeerId, Message)> for alice::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request {
request, channel, ..
} => Self::EncryptedSignatureReceived {
msg: Box::new(request),
channel,
peer,
},
Message::Response { .. } => Self::unexpected_response(peer),
}
}
}
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request { .. } => Self::unexpected_request(peer),
Message::Response { request_id, .. } => {
Self::EncryptedSignatureAcknowledged { id: request_id }
}
}
}
}
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL);

@ -0,0 +1,66 @@
/// Helper macro to map a [`RequestResponseEvent`] to our [`OutEvent`].
///
/// This is primarily a macro and not a regular function because we use it for
/// Alice and Bob and they have different [`OutEvent`]s that just happen to
/// share a couple of variants, like `OutEvent::Failure` and `OutEvent::Other`.
#[macro_export]
macro_rules! impl_from_rr_event {
($protocol_event:ty, $behaviour_out_event:ty, $protocol:ident) => {
impl From<$protocol_event> for $behaviour_out_event {
fn from(event: $protocol_event) -> Self {
use ::libp2p::request_response::RequestResponseEvent::*;
use anyhow::anyhow;
match event {
Message { message, peer, .. } => Self::from((peer, message)),
ResponseSent { .. } => Self::Other,
InboundFailure { peer, error, .. } => {
use libp2p::request_response::InboundFailure::*;
match error {
Timeout => {
Self::Failure {
error: anyhow!("{} failed because of an inbound timeout", $protocol),
peer,
}
}
ConnectionClosed => {
Self::Failure {
error: anyhow!("{} failed because the connection was closed before a response could be sent", $protocol),
peer,
}
}
UnsupportedProtocols => Self::Other, // TODO: Report this and disconnected / ban the peer?
ResponseOmission => Self::Other
}
}
OutboundFailure { peer, error, .. } => {
use libp2p::request_response::OutboundFailure::*;
match error {
Timeout => {
Self::Failure {
error: anyhow!("{} failed because we did not receive a response within the configured timeout", $protocol),
peer,
}
}
ConnectionClosed => {
Self::Failure {
error: anyhow!("{} failed because the connection was closed we received a response", $protocol),
peer,
}
}
UnsupportedProtocols => Self::Other, // TODO: Report this and disconnected / ban the peer?
DialFailure => {
Self::Failure {
error: anyhow!("{} failed because we failed to dial", $protocol),
peer,
}
}
}
}
}
}
}
}
}

@ -1,25 +1,29 @@
use crate::bitcoin;
use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob};
use libp2p::core::ProtocolName;
use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage,
};
use libp2p::PeerId;
use serde::{Deserialize, Serialize};
pub type OutEvent = RequestResponseEvent<(), BidQuote>;
const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/1.0.0";
type OutEvent = RequestResponseEvent<(), BidQuote>;
type Message = RequestResponseMessage<(), BidQuote>;
pub type Behaviour = RequestResponse<CborCodec<BidQuoteProtocol, (), BidQuote>>;
#[derive(Debug, Clone, Copy, Default)]
pub struct BidQuoteProtocol;
impl ProtocolName for BidQuoteProtocol {
fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/bid-quote/1.0.0"
PROTOCOL.as_bytes()
}
}
pub type Message = RequestResponseMessage<(), BidQuote>;
/// Represents a quote for buying XMR.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BidQuote {
@ -31,8 +35,6 @@ pub struct BidQuote {
pub max_quantity: bitcoin::Amount,
}
pub type Behaviour = RequestResponse<CborCodec<BidQuoteProtocol, (), BidQuote>>;
/// Constructs a new instance of the `quote` behaviour to be used by Alice.
///
/// Alice only supports inbound connections, i.e. handing out quotes.
@ -54,3 +56,29 @@ pub fn bob() -> Behaviour {
RequestResponseConfig::default(),
)
}
impl From<(PeerId, Message)> for alice::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request { channel, .. } => Self::QuoteRequested { channel, peer },
Message::Response { .. } => Self::unexpected_response(peer),
}
}
}
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request { .. } => Self::unexpected_request(peer),
Message::Response {
response,
request_id,
} => Self::QuoteReceived {
id: request_id,
response,
},
}
}
}
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL);

@ -1,3 +1,4 @@
use crate::protocol::bob;
use backoff::backoff::Backoff;
use backoff::ExponentialBackoff;
use futures::future::FutureExt;
@ -117,3 +118,13 @@ impl NetworkBehaviour for Behaviour {
})
}
}
impl From<OutEvent> for bob::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::AllAttemptsExhausted { peer } => {
bob::OutEvent::AllRedialAttemptsExhausted { peer }
}
}
}
}

@ -1,13 +1,19 @@
use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob};
use crate::{bitcoin, monero};
use libp2p::core::ProtocolName;
use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage,
};
use libp2p::PeerId;
use serde::{Deserialize, Serialize};
pub type OutEvent = RequestResponseEvent<Request, Response>;
const PROTOCOL: &str = "/comit/xmr/btc/spot-price/1.0.0";
type OutEvent = RequestResponseEvent<Request, Response>;
type Message = RequestResponseMessage<Request, Response>;
pub type Behaviour = RequestResponse<CborCodec<SpotPriceProtocol, Request, Response>>;
/// The spot price protocol allows parties to **initiate** a trade by requesting
/// a spot price.
@ -23,7 +29,7 @@ pub struct SpotPriceProtocol;
impl ProtocolName for SpotPriceProtocol {
fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/spot-price/1.0.0"
PROTOCOL.as_bytes()
}
}
@ -38,10 +44,6 @@ pub struct Response {
pub xmr: monero::Amount,
}
pub type Behaviour = RequestResponse<CborCodec<SpotPriceProtocol, Request, Response>>;
pub type Message = RequestResponseMessage<Request, Response>;
/// Constructs a new instance of the `spot-price` behaviour to be used by Alice.
///
/// Alice only supports inbound connections, i.e. providing spot prices for BTC
@ -65,3 +67,35 @@ pub fn bob() -> Behaviour {
RequestResponseConfig::default(),
)
}
impl From<(PeerId, Message)> for alice::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request {
request, channel, ..
} => Self::SpotPriceRequested {
request,
channel,
peer,
},
Message::Response { .. } => Self::unexpected_response(peer),
}
}
}
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request { .. } => Self::unexpected_request(peer),
Message::Response {
response,
request_id,
} => Self::SpotPriceReceived {
id: request_id,
response,
},
}
}
}
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL);

@ -1,21 +1,27 @@
use crate::monero;
use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob};
use libp2p::core::ProtocolName;
use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage,
};
use libp2p::PeerId;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub type OutEvent = RequestResponseEvent<Request, ()>;
const PROTOCOL: &str = "/comit/xmr/btc/transfer_proof/1.0.0";
type OutEvent = RequestResponseEvent<Request, ()>;
type Message = RequestResponseMessage<Request, ()>;
pub type Behaviour = RequestResponse<CborCodec<TransferProofProtocol, Request, ()>>;
#[derive(Debug, Clone, Copy, Default)]
pub struct TransferProofProtocol;
impl ProtocolName for TransferProofProtocol {
fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/transfer_proof/1.0.0"
PROTOCOL.as_bytes()
}
}
@ -25,10 +31,6 @@ pub struct Request {
pub tx_lock_proof: monero::TransferProof,
}
pub type Behaviour = RequestResponse<CborCodec<TransferProofProtocol, Request, ()>>;
pub type Message = RequestResponseMessage<Request, ()>;
pub fn alice() -> Behaviour {
Behaviour::new(
CborCodec::default(),
@ -44,3 +46,31 @@ pub fn bob() -> Behaviour {
RequestResponseConfig::default(),
)
}
impl From<(PeerId, Message)> for alice::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request { .. } => Self::unexpected_request(peer),
Message::Response { request_id, .. } => Self::TransferProofAcknowledged {
peer,
id: request_id,
},
}
}
}
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self {
match message {
Message::Request {
request, channel, ..
} => Self::TransferProofReceived {
msg: Box::new(request),
channel,
},
Message::Response { .. } => Self::unexpected_response(peer),
}
}
}
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL);

@ -2,9 +2,7 @@ use crate::network::quote::BidQuote;
use crate::network::{encrypted_signature, quote, spot_price, transfer_proof};
use crate::protocol::alice::{execution_setup, State3};
use anyhow::{anyhow, Error};
use libp2p::request_response::{
RequestId, RequestResponseEvent, RequestResponseMessage, ResponseChannel,
};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId};
use uuid::Uuid;
@ -33,22 +31,24 @@ pub enum OutEvent {
channel: ResponseChannel<()>,
peer: PeerId,
},
ResponseSent, // Same variant is used for all messages as no processing is done
Failure {
peer: PeerId,
error: Error,
},
/// "Fallback" variant that allows the event mapping code to swallow certain
/// events that we don't want the caller to deal with.
Other,
}
impl OutEvent {
fn unexpected_request(peer: PeerId) -> OutEvent {
pub fn unexpected_request(peer: PeerId) -> OutEvent {
OutEvent::Failure {
peer,
error: anyhow!("Unexpected request received"),
}
}
fn unexpected_response(peer: PeerId) -> OutEvent {
pub fn unexpected_response(peer: PeerId) -> OutEvent {
OutEvent::Failure {
peer,
error: anyhow!("Unexpected response received"),
@ -56,121 +56,6 @@ impl OutEvent {
}
}
impl From<(PeerId, quote::Message)> for OutEvent {
fn from((peer, message): (PeerId, quote::Message)) -> Self {
match message {
quote::Message::Request { channel, .. } => OutEvent::QuoteRequested { channel, peer },
quote::Message::Response { .. } => OutEvent::unexpected_response(peer),
}
}
}
impl From<(PeerId, spot_price::Message)> for OutEvent {
fn from((peer, message): (PeerId, spot_price::Message)) -> Self {
match message {
spot_price::Message::Request {
request, channel, ..
} => OutEvent::SpotPriceRequested {
request,
channel,
peer,
},
spot_price::Message::Response { .. } => OutEvent::unexpected_response(peer),
}
}
}
impl From<(PeerId, transfer_proof::Message)> for OutEvent {
fn from((peer, message): (PeerId, transfer_proof::Message)) -> Self {
match message {
transfer_proof::Message::Request { .. } => OutEvent::unexpected_request(peer),
transfer_proof::Message::Response { request_id, .. } => {
OutEvent::TransferProofAcknowledged {
peer,
id: request_id,
}
}
}
}
}
impl From<(PeerId, encrypted_signature::Message)> for OutEvent {
fn from((peer, message): (PeerId, encrypted_signature::Message)) -> Self {
match message {
encrypted_signature::Message::Request {
request, channel, ..
} => OutEvent::EncryptedSignatureReceived {
msg: Box::new(request),
channel,
peer,
},
encrypted_signature::Message::Response { .. } => OutEvent::unexpected_response(peer),
}
}
}
impl From<spot_price::OutEvent> for OutEvent {
fn from(event: spot_price::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<quote::OutEvent> for OutEvent {
fn from(event: quote::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<transfer_proof::OutEvent> for OutEvent {
fn from(event: transfer_proof::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<encrypted_signature::OutEvent> for OutEvent {
fn from(event: encrypted_signature::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
fn map_rr_event_to_outevent<I, O>(event: RequestResponseEvent<I, O>) -> OutEvent
where
OutEvent: From<(PeerId, RequestResponseMessage<I, O>)>,
{
use RequestResponseEvent::*;
match event {
Message { message, peer, .. } => OutEvent::from((peer, message)),
ResponseSent { .. } => OutEvent::ResponseSent,
InboundFailure { peer, error, .. } => OutEvent::Failure {
error: anyhow!("protocol failed due to {:?}", error),
peer,
},
OutboundFailure { peer, error, .. } => OutEvent::Failure {
error: anyhow!("protocol failed due to {:?}", error),
peer,
},
}
}
impl From<execution_setup::OutEvent> for OutEvent {
fn from(event: execution_setup::OutEvent) -> Self {
use crate::protocol::alice::execution_setup::OutEvent::*;
match event {
Done {
bob_peer_id,
swap_id,
state3,
} => OutEvent::ExecutionSetupDone {
bob_peer_id,
swap_id,
state3: Box::new(state3),
},
Failure { peer, error } => OutEvent::Failure { peer, error },
}
}
}
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]

@ -218,7 +218,6 @@ where
channel
}.boxed());
}
SwarmEvent::Behaviour(OutEvent::ResponseSent) => {}
SwarmEvent::Behaviour(OutEvent::Failure {peer, error}) => {
tracing::error!(%peer, "Communication error: {:#}", error);
}

@ -1,6 +1,6 @@
use crate::network::cbor_request_response::BUF_SIZE;
use crate::protocol::alice::{State0, State3};
use crate::protocol::{Message0, Message2, Message4};
use crate::protocol::{alice, Message0, Message2, Message4};
use anyhow::{Context, Error};
use libp2p::PeerId;
use libp2p_async_await::BehaviourOutEvent;
@ -86,3 +86,20 @@ impl Behaviour {
})
}
}
impl From<OutEvent> for alice::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::Done {
bob_peer_id,
state3,
swap_id,
} => Self::ExecutionSetupDone {
bob_peer_id,
state3: Box::new(state3),
swap_id,
},
OutEvent::Failure { peer, error } => Self::Failure { peer, error },
}
}
}

@ -6,9 +6,7 @@ use crate::protocol::bob;
use crate::{bitcoin, monero};
use anyhow::{anyhow, Error, Result};
use libp2p::core::Multiaddr;
use libp2p::request_response::{
RequestId, RequestResponseEvent, RequestResponseMessage, ResponseChannel,
};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId};
use std::sync::Arc;
use uuid::Uuid;
@ -128,135 +126,27 @@ pub enum OutEvent {
AllRedialAttemptsExhausted {
peer: PeerId,
},
ResponseSent, // Same variant is used for all messages as no processing is done
CommunicationError(Error),
Failure {
peer: PeerId,
error: Error,
},
/// "Fallback" variant that allows the event mapping code to swallow certain
/// events that we don't want the caller to deal with.
Other,
}
impl OutEvent {
fn unexpected_request() -> OutEvent {
OutEvent::CommunicationError(anyhow!("Unexpected request received"))
}
fn unexpected_response() -> OutEvent {
OutEvent::CommunicationError(anyhow!("Unexpected response received"))
}
}
impl From<quote::Message> for OutEvent {
fn from(message: quote::Message) -> Self {
match message {
quote::Message::Request { .. } => OutEvent::unexpected_request(),
quote::Message::Response {
response,
request_id,
} => OutEvent::QuoteReceived {
id: request_id,
response,
},
}
}
}
impl From<spot_price::Message> for OutEvent {
fn from(message: spot_price::Message) -> Self {
match message {
spot_price::Message::Request { .. } => OutEvent::unexpected_request(),
spot_price::Message::Response {
response,
request_id,
} => OutEvent::SpotPriceReceived {
id: request_id,
response,
},
}
}
}
impl From<transfer_proof::Message> for OutEvent {
fn from(message: transfer_proof::Message) -> Self {
match message {
transfer_proof::Message::Request {
request, channel, ..
} => OutEvent::TransferProofReceived {
msg: Box::new(request),
channel,
},
transfer_proof::Message::Response { .. } => OutEvent::unexpected_response(),
}
}
}
impl From<encrypted_signature::Message> for OutEvent {
fn from(message: encrypted_signature::Message) -> Self {
match message {
encrypted_signature::Message::Request { .. } => OutEvent::unexpected_request(),
encrypted_signature::Message::Response { request_id, .. } => {
OutEvent::EncryptedSignatureAcknowledged { id: request_id }
}
}
}
}
impl From<spot_price::OutEvent> for OutEvent {
fn from(event: spot_price::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<quote::OutEvent> for OutEvent {
fn from(event: quote::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<transfer_proof::OutEvent> for OutEvent {
fn from(event: transfer_proof::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<encrypted_signature::OutEvent> for OutEvent {
fn from(event: encrypted_signature::OutEvent) -> Self {
map_rr_event_to_outevent(event)
}
}
impl From<redial::OutEvent> for OutEvent {
fn from(event: redial::OutEvent) -> Self {
match event {
redial::OutEvent::AllAttemptsExhausted { peer } => {
OutEvent::AllRedialAttemptsExhausted { peer }
}
pub fn unexpected_request(peer: PeerId) -> OutEvent {
OutEvent::Failure {
peer,
error: anyhow!("Unexpected request received"),
}
}
}
fn map_rr_event_to_outevent<I, O>(event: RequestResponseEvent<I, O>) -> OutEvent
where
OutEvent: From<RequestResponseMessage<I, O>>,
{
use RequestResponseEvent::*;
match event {
Message { message, .. } => OutEvent::from(message),
ResponseSent { .. } => OutEvent::ResponseSent,
InboundFailure { peer, error, .. } => OutEvent::CommunicationError(anyhow!(
"protocol with peer {} failed due to {:?}",
peer,
error
)),
OutboundFailure { peer, error, .. } => OutEvent::CommunicationError(anyhow!(
"protocol with peer {} failed due to {:?}",
pub fn unexpected_response(peer: PeerId) -> OutEvent {
OutEvent::Failure {
peer,
error
)),
}
}
impl From<execution_setup::OutEvent> for OutEvent {
fn from(event: execution_setup::OutEvent) -> Self {
match event {
execution_setup::OutEvent::Done(res) => OutEvent::ExecutionSetupDone(Box::new(res)),
error: anyhow!("Unexpected response received"),
}
}
}

@ -151,11 +151,8 @@ impl EventLoop {
tracing::error!("Exhausted all re-dial attempts to Alice");
return;
}
SwarmEvent::Behaviour(OutEvent::ResponseSent) => {
}
SwarmEvent::Behaviour(OutEvent::CommunicationError(error)) => {
tracing::warn!("Communication error: {:#}", error);
SwarmEvent::Behaviour(OutEvent::Failure { peer, error }) => {
tracing::warn!(%peer, "Communication error: {:#}", error);
return;
}
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } if peer_id == self.alice_peer_id => {

@ -1,6 +1,6 @@
use crate::network::cbor_request_response::BUF_SIZE;
use crate::protocol::bob::{State0, State2};
use crate::protocol::{Message1, Message3};
use crate::protocol::{bob, Message1, Message3};
use anyhow::{Context, Error, Result};
use libp2p::PeerId;
use libp2p_async_await::BehaviourOutEvent;
@ -85,3 +85,11 @@ impl Behaviour {
})
}
}
impl From<OutEvent> for bob::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::Done(res) => Self::ExecutionSetupDone(Box::new(res)),
}
}
}

Loading…
Cancel
Save