cross_domain_message_gossip/
gossip_worker.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
use futures::{FutureExt, StreamExt};
use parity_scale_codec::{Decode, Encode};
use parking_lot::{Mutex, RwLock};
use sc_network::config::NonDefaultSetConfig;
use sc_network::{NetworkPeers, NotificationService, PeerId};
use sc_network_gossip::{
    GossipEngine, MessageIntent, Syncing as GossipSyncing, ValidationResult, Validator,
    ValidatorContext,
};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use sp_api::StorageProof;
use sp_consensus::SyncOracle;
use sp_core::twox_256;
use sp_messenger::messages::{ChainId, ChannelId};
use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT};
use std::collections::{BTreeMap, HashSet};
use std::future::poll_fn;
use std::pin::pin;
use std::sync::Arc;
use subspace_runtime_primitives::BlockNumber;

const LOG_TARGET: &str = "cross_chain_gossip_worker";
const PROTOCOL_NAME: &str = "/subspace/cross-chain-messages";

/// Encoded message with sender info if available.
pub struct ChainMsg {
    pub maybe_peer: Option<PeerId>,
    pub data: MessageData,
}

/// Unbounded sender to send encoded message to listeners.
pub type ChainSink = TracingUnboundedSender<ChainMsg>;
type MessageHash = [u8; 32];

/// Channel update message.
#[derive(Debug, Encode, Decode)]
pub struct ChannelUpdate {
    /// Message is coming from src_chain.
    pub src_chain_id: ChainId,
    /// Channel id.
    pub channel_id: ChannelId,
    /// Block number at which storage proof was generated.
    pub block_number: BlockNumber,
    /// Storage proof of the channel on src_chain.
    pub storage_proof: StorageProof,
}

/// A type of cross chain message
#[derive(Debug, Encode, Decode)]
pub enum MessageData {
    /// Encoded XDM message
    Xdm(Vec<u8>),
    /// Encoded channel update message.
    ChannelUpdate(ChannelUpdate),
}

/// A cross chain message with encoded data.
#[derive(Debug, Encode, Decode)]
pub struct Message {
    pub chain_id: ChainId,
    pub data: MessageData,
}

/// Gossip worker builder
pub struct GossipWorkerBuilder {
    gossip_msg_stream: TracingUnboundedReceiver<Message>,
    gossip_msg_sink: TracingUnboundedSender<Message>,
    chain_sinks: BTreeMap<ChainId, ChainSink>,
}

impl GossipWorkerBuilder {
    /// Construct a gossip worker builder
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        let (gossip_msg_sink, gossip_msg_stream) =
            tracing_unbounded("cross_chain_gossip_messages", 100);
        Self {
            gossip_msg_stream,
            gossip_msg_sink,
            chain_sinks: BTreeMap::new(),
        }
    }

    /// Collect the chain sink that will be used by the gossip message worker later.
    pub fn push_chain_sink(&mut self, chain_id: ChainId, sink: ChainSink) {
        self.chain_sinks.insert(chain_id, sink);
    }

    // Remove the chain sink
    pub fn remove_chain_sink(&mut self, chain_id: &ChainId) -> Option<ChainSink> {
        self.chain_sinks.remove(chain_id)
    }

    /// Get the gossip message sink
    pub fn gossip_msg_sink(&self) -> TracingUnboundedSender<Message> {
        self.gossip_msg_sink.clone()
    }

    /// Build gossip worker
    pub fn build<Block, Network, GossipSync>(
        self,
        network: Network,
        notification_service: Box<dyn NotificationService>,
        sync: Arc<GossipSync>,
    ) -> GossipWorker<Block, Network, GossipSync>
    where
        Block: BlockT,
        Network: sc_network_gossip::Network<Block> + Send + Sync + Clone + 'static,
        GossipSync: GossipSyncing<Block> + SyncOracle + Send + 'static,
    {
        let Self {
            gossip_msg_stream,
            chain_sinks,
            ..
        } = self;

        let gossip_validator = Arc::new(GossipValidator::new(network.clone()));
        let gossip_engine = Arc::new(Mutex::new(GossipEngine::new(
            network,
            sync.clone(),
            notification_service,
            PROTOCOL_NAME,
            gossip_validator.clone(),
            None,
        )));

        GossipWorker {
            gossip_engine,
            gossip_validator,
            gossip_msg_stream,
            chain_sinks,
            sync_oracle: sync,
        }
    }
}

/// Gossip worker to gossip incoming and outgoing messages to other peers.
/// Also, streams the decoded extrinsics to destination chain tx pool if available.
pub struct GossipWorker<Block: BlockT, Network, SO> {
    gossip_engine: Arc<Mutex<GossipEngine<Block>>>,
    gossip_validator: Arc<GossipValidator<Network>>,
    gossip_msg_stream: TracingUnboundedReceiver<Message>,
    chain_sinks: BTreeMap<ChainId, ChainSink>,
    sync_oracle: Arc<SO>,
}

/// Returns the network configuration for cross chain message gossip.
pub fn xdm_gossip_peers_set_config() -> (NonDefaultSetConfig, Box<dyn NotificationService>) {
    let (mut cfg, notification_service) = NonDefaultSetConfig::new(
        PROTOCOL_NAME.into(),
        Vec::new(),
        5 * 1024 * 1024,
        None,
        Default::default(),
    );
    cfg.allow_non_reserved(25, 25);
    (cfg, notification_service)
}

/// Cross chain message topic.
fn topic<Block: BlockT>() -> Block::Hash {
    <Block::Header as HeaderT>::Hashing::hash(b"cross-chain-messages")
}

impl<Block: BlockT, Network, SO: SyncOracle> GossipWorker<Block, Network, SO> {
    /// Starts the Gossip message worker.
    pub async fn run(mut self) {
        let incoming_cross_chain_messages = pin!(self
            .gossip_engine
            .lock()
            .messages_for(topic::<Block>())
            .filter_map(|notification| async move {
                Message::decode(&mut &notification.message[..])
                    .ok()
                    .map(|msg| (notification.sender, msg))
            }));
        let mut incoming_cross_chain_messages = incoming_cross_chain_messages.fuse();

        loop {
            let engine = self.gossip_engine.clone();
            let mut gossip_engine = poll_fn(|cx| engine.lock().poll_unpin(cx)).fuse();

            futures::select! {
                cross_chain_message = incoming_cross_chain_messages.next() => {
                    if let Some((maybe_peer, msg)) = cross_chain_message {
                        tracing::debug!(target: LOG_TARGET, "Incoming cross chain message for chain from Network: {:?}", msg.chain_id);
                        self.handle_cross_chain_message(msg, maybe_peer);
                    }
                },

                msg = self.gossip_msg_stream.select_next_some() => {
                    tracing::debug!(target: LOG_TARGET, "Incoming cross chain message for chain from Relayer: {:?}", msg.chain_id);
                    self.handle_cross_chain_message(msg, None);
                }

                _ = gossip_engine => {
                    tracing::error!(target: LOG_TARGET, "Gossip engine has terminated.");
                    return;
                }
            }
        }
    }

    fn handle_cross_chain_message(&mut self, msg: Message, maybe_peer: Option<PeerId>) {
        // mark and rebroadcast message
        let encoded_msg = msg.encode();
        self.gossip_validator.note_broadcast(&encoded_msg);
        self.gossip_engine
            .lock()
            .gossip_message(topic::<Block>(), encoded_msg, false);

        // Skip sending the message since the node unable to verify the message before synced
        if self.sync_oracle.is_major_syncing() {
            return;
        }

        let Message { chain_id, data } = msg;
        let sink = match self.chain_sinks.get(&chain_id) {
            Some(sink) => sink,
            None => return,
        };

        // send the message to the open and ready channel
        if !sink.is_closed() && sink.unbounded_send(ChainMsg { data, maybe_peer }).is_ok() {
            return;
        }

        // sink is either closed or failed to send unbounded message
        // consider it closed and remove the sink.
        tracing::error!(
            target: LOG_TARGET,
            "Failed to send incoming chain message: {:?}",
            chain_id
        );
        self.chain_sinks.remove(&chain_id);
    }
}

/// Gossip validator to retain or clean up Gossiped messages.
#[derive(Debug)]
struct GossipValidator<Network> {
    network: Network,
    should_broadcast: RwLock<HashSet<MessageHash>>,
}

impl<Network> GossipValidator<Network> {
    fn new(network: Network) -> Self {
        Self {
            network,
            should_broadcast: Default::default(),
        }
    }

    fn note_broadcast(&self, msg: &[u8]) {
        let msg_hash = twox_256(msg);
        let mut msg_set = self.should_broadcast.write();
        msg_set.insert(msg_hash);
    }

    fn should_broadcast(&self, msg: &[u8]) -> bool {
        let msg_hash = twox_256(msg);
        let msg_set = self.should_broadcast.read();
        msg_set.contains(&msg_hash)
    }

    fn note_broadcasted(&self, msg: &[u8]) {
        let msg_hash = twox_256(msg);
        let mut msg_set = self.should_broadcast.write();
        msg_set.remove(&msg_hash);
    }
}

impl<Block, Network> Validator<Block> for GossipValidator<Network>
where
    Block: BlockT,
    Network: NetworkPeers + Send + Sync + 'static,
{
    fn validate(
        &self,
        _context: &mut dyn ValidatorContext<Block>,
        sender: &PeerId,
        mut data: &[u8],
    ) -> ValidationResult<Block::Hash> {
        match Message::decode(&mut data) {
            Ok(_) => ValidationResult::ProcessAndKeep(topic::<Block>()),
            Err(_) => {
                self.network.report_peer(*sender, rep::GOSSIP_NOT_DECODABLE);
                ValidationResult::Discard
            }
        }
    }

    fn message_expired<'a>(&'a self) -> Box<dyn FnMut(Block::Hash, &[u8]) -> bool + 'a> {
        Box::new(move |_topic, data| !self.should_broadcast(data))
    }

    fn message_allowed<'a>(
        &'a self,
    ) -> Box<dyn FnMut(&PeerId, MessageIntent, &Block::Hash, &[u8]) -> bool + 'a> {
        Box::new(move |_who, _intent, _topic, data| {
            let should_broadcast = self.should_broadcast(data);
            if should_broadcast {
                self.note_broadcasted(data)
            }

            should_broadcast
        })
    }
}

pub(crate) mod rep {
    use sc_network::ReputationChange;

    /// Reputation change when a peer sends us a gossip message that can't be decoded.
    pub(crate) const GOSSIP_NOT_DECODABLE: ReputationChange =
        ReputationChange::new_fatal("Cross chain message: not decodable");
}