cfxcore/pos/mempool/shared_mempool/
tasks.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8//! Tasks that are executed by coordinators (short-lived compared to
9//! coordinators)
10
11use crate::pos::mempool::{
12    core_mempool::{CoreMempool, TimelineState, TxnPointer},
13    logging::{LogEntry, LogEvent, LogSchema},
14    network::MempoolSyncMsg,
15    shared_mempool::types::{
16        notify_subscribers, ScheduledBroadcast, SharedMempool,
17        SharedMempoolNotification, SubmissionStatusBundle,
18    },
19    CommitNotification, CommitResponse, CommittedTransaction, ConsensusRequest,
20    ConsensusResponse, SubmissionStatus,
21};
22use anyhow::Result;
23use cached_pos_ledger_db::CachedPosLedgerDB;
24use diem_logger::prelude::*;
25use diem_types::{
26    mempool_status::{MempoolStatus, MempoolStatusCode},
27    transaction::SignedTransaction,
28};
29use futures::{channel::oneshot, stream::FuturesUnordered};
30use network::node_table::NodeId;
31use parking_lot::Mutex;
32use rayon::prelude::*;
33use std::{
34    cmp,
35    collections::HashSet,
36    sync::Arc,
37    time::{Duration, Instant},
38};
39use tokio::runtime::Handle;
40
41// ============================== //
42//  broadcast_coordinator tasks  //
43// ============================== //
44
45/// Attempts broadcast to `peer` and schedules the next broadcast.
46pub(crate) fn execute_broadcast(
47    peer: NodeId, backoff: bool, smp: &mut SharedMempool,
48    scheduled_broadcasts: &mut FuturesUnordered<ScheduledBroadcast>,
49    broadcasting_peers: &mut HashSet<NodeId>, executor: Handle,
50) {
51    diem_trace!("execute_broadcast starts: peer={}", peer);
52    let peer_manager = &smp.peer_manager.clone();
53    peer_manager.execute_broadcast(peer.clone(), backoff, smp);
54    let schedule_backoff = peer_manager.is_backoff_mode(&peer);
55
56    let interval_ms = if schedule_backoff {
57        smp.config.shared_mempool_backoff_interval_ms
58    } else {
59        smp.config.shared_mempool_tick_interval_ms
60    };
61
62    if peer_manager.contains_peer(&peer) {
63        // Make sure we only has one broadcast task for one peer id.
64        broadcasting_peers.insert(peer);
65        scheduled_broadcasts.push(ScheduledBroadcast::new(
66            Instant::now() + Duration::from_millis(interval_ms),
67            peer,
68            schedule_backoff,
69            executor,
70        ));
71    } else {
72        // The peer has been disconnected,
73        // so it can be added again after reconnection.
74        broadcasting_peers.remove(&peer);
75    }
76    diem_trace!("execute_broadcast end: peer={}", peer);
77}
78
79// =============================== //
80// Tasks processing txn submission //
81// =============================== //
82
83/// Processes transactions directly submitted by client.
84pub(crate) async fn process_client_transaction_submission(
85    smp: SharedMempool, transaction: SignedTransaction,
86    callback: oneshot::Sender<Result<SubmissionStatus>>,
87) {
88    let statuses = process_incoming_transactions(
89        &smp,
90        vec![transaction],
91        TimelineState::NotReady,
92    )
93    .await;
94    log_txn_process_results(&statuses, None);
95
96    if let Some(status) = statuses.get(0) {
97        if callback.send(Ok(status.1.clone())).is_err() {
98            diem_error!(LogSchema::event_log(
99                LogEntry::JsonRpc,
100                LogEvent::CallbackFail
101            ));
102        }
103    }
104}
105
106/// Processes transactions from other nodes.
107pub(crate) async fn process_transaction_broadcast(
108    smp: SharedMempool, transactions: Vec<SignedTransaction>,
109    request_id: Vec<u8>, timeline_state: TimelineState, peer: NodeId,
110) {
111    diem_trace!("process_transaction_broadcast starts: peer={}", peer);
112    let results = process_incoming_transactions(
113        &smp,
114        transactions.clone(),
115        timeline_state,
116    )
117    .await;
118    log_txn_process_results(&results, Some(peer.clone()));
119
120    let ack_response = gen_ack_response(request_id, results, &peer);
121    if let Err(e) = smp
122        .network_sender
123        .send_message_with_peer_id(&peer, &ack_response)
124    {
125        diem_error!(LogSchema::event_log(
126            LogEntry::BroadcastACK,
127            LogEvent::NetworkSendFail
128        )
129        .error(&e.into()));
130        return;
131    }
132    notify_subscribers(SharedMempoolNotification::ACK, &smp.subscribers);
133    diem_trace!("process_transaction_broadcast ends: peer={}", peer);
134}
135
136fn gen_ack_response(
137    request_id: Vec<u8>, results: Vec<SubmissionStatusBundle>, peer: &NodeId,
138) -> MempoolSyncMsg {
139    // No global cap (`MempoolIsFull` is unreachable), so `backoff`
140    // stays off. Retry on the per-sender cap; GC or commit frees a slot.
141    let retry = results.iter().any(|(_, (status, _))| {
142        matches!(status.code, MempoolStatusCode::TooManyTransactions)
143    });
144
145    diem_trace!(
146        "request[{:?}] from peer[{:?}] retry[{:?}]",
147        request_id,
148        peer,
149        retry
150    );
151
152    MempoolSyncMsg::BroadcastTransactionsResponse {
153        request_id,
154        retry,
155        backoff: false,
156    }
157}
158
159/// Submits a list of SignedTransaction to the local mempool
160/// and returns a vector containing AdmissionControlStatus.
161pub(crate) async fn process_incoming_transactions(
162    smp: &SharedMempool, transactions: Vec<SignedTransaction>,
163    timeline_state: TimelineState,
164) -> Vec<SubmissionStatusBundle> {
165    let mut statuses = vec![];
166
167    // Filter out already received transactions, so we do not need to process
168    // them.
169    let transactions: Vec<SignedTransaction> = {
170        let mempool = smp.mempool.lock();
171        transactions
172            .into_iter()
173            .filter(|tx| mempool.transactions.get(&tx.hash()).is_none())
174            .collect()
175    };
176    let pos_state = smp.db_with_cache.db.reader.get_latest_pos_state();
177    let validation_results = transactions
178        .par_iter()
179        .map(|t| smp.validator.read().validate_transaction(&t, &pos_state))
180        .collect::<Vec<_>>();
181
182    {
183        let mut mempool = smp.mempool.lock();
184        for (idx, transaction) in transactions.into_iter().enumerate() {
185            match validation_results[idx] {
186                None => {
187                    let mempool_status =
188                        mempool.add_txn(transaction.clone(), timeline_state);
189                    statuses.push((transaction, (mempool_status, None)));
190                }
191                Some(validation_status) => {
192                    statuses.push((
193                        transaction,
194                        (
195                            MempoolStatus::new(MempoolStatusCode::VmError),
196                            Some(validation_status),
197                        ),
198                    ));
199                }
200            }
201        }
202    }
203    notify_subscribers(
204        SharedMempoolNotification::NewTransactions,
205        &smp.subscribers,
206    );
207    statuses
208}
209
210fn log_txn_process_results(
211    results: &[SubmissionStatusBundle], sender: Option<NodeId>,
212) {
213    let sender = match sender {
214        Some(peer) => peer,
215        None => {
216            return;
217        }
218    };
219    for (txn, (_mempool_status, maybe_vm_status)) in results.iter() {
220        if let Some(vm_status) = maybe_vm_status {
221            diem_trace!(
222                SecurityEvent::InvalidTransactionMempool,
223                failed_transaction = txn,
224                vm_status = vm_status,
225                sender = sender,
226            );
227        }
228    }
229}
230
231// ================================= //
232// intra-node communication handlers //
233// ================================= //
234
235pub(crate) async fn process_committed_transactions(
236    mempool: Arc<Mutex<CoreMempool>>, req: CommitNotification,
237) {
238    diem_debug!(LogSchema::event_log(
239        LogEntry::StateSyncCommit,
240        LogEvent::Received
241    )
242    .state_sync_msg(&req));
243    commit_txns(&mempool, req.transactions).await;
244    if req.callback.send(Ok(CommitResponse::success())).is_err() {
245        diem_error!(LogSchema::event_log(
246            LogEntry::StateSyncCommit,
247            LogEvent::CallbackFail
248        ));
249    }
250}
251
252pub(crate) async fn process_consensus_request(
253    db: Arc<CachedPosLedgerDB>, mempool: &Mutex<CoreMempool>,
254    req: ConsensusRequest,
255) {
256    diem_debug!(
257        LogSchema::event_log(LogEntry::Consensus, LogEvent::Received)
258            .consensus_msg(&req)
259    );
260
261    let ConsensusRequest {
262        max_block_size,
263        exclude_txns,
264        parent_block_id,
265        validators,
266        callback,
267    } = req;
268    let exclude_transactions: HashSet<TxnPointer> = exclude_txns
269        .iter()
270        .map(|txn| (txn.sender, txn.hash))
271        .collect();
272    let mut txns;
273    {
274        let mut mempool = mempool.lock();
275        let block_size = cmp::max(max_block_size, 1);
276        let pos_state = db
277            .get_pos_state(&parent_block_id)
278            .expect("pos_state should exist");
279        txns = mempool.get_block(
280            block_size,
281            exclude_transactions,
282            &pos_state,
283            validators,
284        );
285    }
286    let pulled_block = txns.drain(..).map(SignedTransaction::into).collect();
287    let resp = ConsensusResponse { txns: pulled_block };
288    if callback.send(Ok(resp)).is_err() {
289        diem_error!(LogSchema::event_log(
290            LogEntry::Consensus,
291            LogEvent::CallbackFail
292        ));
293    }
294}
295
296async fn commit_txns(
297    mempool: &Mutex<CoreMempool>, transactions: Vec<CommittedTransaction>,
298) {
299    let mut pool = mempool.lock();
300
301    for transaction in transactions {
302        pool.remove_transaction(transaction.hash);
303    }
304}