node/transaction_pool/
transaction_pool_actions.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use ledger::{
4    scan_state::transaction_logic::valid,
5    transaction_pool::{
6        diff::{self, BestTipDiff, DiffVerified},
7        ValidCommandWithHash,
8    },
9    Account, AccountId,
10};
11use mina_p2p_messages::{
12    list::List,
13    v2::{self},
14};
15use openmina_core::{
16    transaction::{TransactionPoolMessageSource, TransactionWithHash},
17    ActionEvent,
18};
19use redux::Callback;
20use serde::{Deserialize, Serialize};
21
22use super::{candidate::TransactionPoolCandidateAction, PendingId};
23
24pub type TransactionPoolActionWithMeta = redux::ActionWithMeta<TransactionPoolAction>;
25pub type TransactionPoolActionWithMetaRef<'a> = redux::ActionWithMeta<&'a TransactionPoolAction>;
26
27#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)]
28#[action_event(level = info)]
29pub enum TransactionPoolAction {
30    Candidate(TransactionPoolCandidateAction),
31    StartVerify {
32        commands: List<TransactionWithHash>,
33        from_source: TransactionPoolMessageSource,
34    },
35    StartVerifyWithAccounts {
36        accounts: BTreeMap<AccountId, Account>,
37        pending_id: PendingId,
38        from_source: TransactionPoolMessageSource,
39    },
40    VerifySuccess {
41        valids: Vec<valid::UserCommand>,
42        from_source: TransactionPoolMessageSource,
43    },
44    #[action_event(level = warn, fields(debug(errors)))]
45    VerifyError {
46        errors: Vec<String>,
47    },
48    BestTipChanged {
49        best_tip_hash: v2::LedgerHash,
50    },
51    BestTipChangedWithAccounts {
52        accounts: BTreeMap<AccountId, Account>,
53    },
54    ApplyVerifiedDiff {
55        best_tip_hash: v2::LedgerHash,
56        diff: DiffVerified,
57        from_source: TransactionPoolMessageSource,
58    },
59    ApplyVerifiedDiffWithAccounts {
60        accounts: BTreeMap<AccountId, Account>,
61        pending_id: PendingId,
62    },
63    ApplyTransitionFrontierDiff {
64        best_tip_hash: v2::LedgerHash,
65        diff: BestTipDiff,
66    },
67    ApplyTransitionFrontierDiffWithAccounts {
68        accounts: BTreeMap<AccountId, Account>,
69        pending_id: PendingId,
70    },
71    /// Rebroadcast locally generated pool items every 10 minutes. Do so for 50
72    /// minutes - at most 5 rebroadcasts - before giving up.
73    Rebroadcast {
74        accepted: Vec<ValidCommandWithHash>,
75        rejected: Vec<(ValidCommandWithHash, diff::Error)>,
76        is_local: bool,
77    },
78    CollectTransactionsByFee,
79    #[action_event(level = trace)]
80    P2pSendAll,
81    #[action_event(level = debug)]
82    P2pSend {
83        peer_id: p2p::PeerId,
84    },
85}
86
87impl redux::EnablingCondition<crate::State> for TransactionPoolAction {
88    fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
89        match self {
90            TransactionPoolAction::Candidate(a) => a.is_enabled(state, time),
91            TransactionPoolAction::StartVerify { commands, .. } => {
92                !commands.is_empty()
93                    && commands
94                        .iter()
95                        .any(|cmd| !state.transaction_pool.contains(cmd.hash()))
96            }
97            TransactionPoolAction::P2pSendAll => true,
98            TransactionPoolAction::P2pSend { peer_id } => state
99                .p2p
100                .get_ready_peer(peer_id)
101                // can't propagate empty transaction pool
102                .filter(|_| !state.transaction_pool.dpool.is_empty())
103                // Only send transactions if peer has the same best tip,
104                // or its best tip is extension of our best tip.
105                .and_then(|p| {
106                    let peer_best_tip = p.best_tip.as_ref()?;
107                    let our_best_tip = state.transition_frontier.best_tip()?.hash();
108                    Some(p).filter(|_| {
109                        peer_best_tip.hash() == our_best_tip
110                            || peer_best_tip.pred_hash() == our_best_tip
111                    })
112                })
113                .is_some_and(|p| {
114                    let check =
115                        |(next_index, limit), last_index| limit > 0 && next_index <= last_index;
116                    let last_index = state.transaction_pool.dpool.last_index();
117
118                    check(
119                        p.channels.transaction.next_send_index_and_limit(),
120                        last_index,
121                    )
122                }),
123            TransactionPoolAction::Rebroadcast {
124                accepted, rejected, ..
125            } => !(accepted.is_empty() && rejected.is_empty()),
126            _ => true,
127        }
128    }
129}
130
131type TransactionPoolEffectfulActionCallback = Callback<(
132    BTreeMap<AccountId, Account>,
133    Option<PendingId>,
134    TransactionPoolMessageSource,
135)>;
136
137#[derive(Serialize, Deserialize, Debug, Clone)]
138pub enum TransactionPoolEffectfulAction {
139    FetchAccounts {
140        account_ids: BTreeSet<AccountId>,
141        ledger_hash: v2::LedgerHash,
142        on_result: TransactionPoolEffectfulActionCallback,
143        pending_id: Option<PendingId>,
144        from_source: TransactionPoolMessageSource,
145    },
146}
147
148impl redux::EnablingCondition<crate::State> for TransactionPoolEffectfulAction {}