iota_core/authority/
transaction_deferral.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use iota_types::base_types::{CommitRound, ObjectID};
6use serde::{Deserialize, Serialize};
7
8#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
9pub enum DeferralKey {
10    // For transactions deferred until new randomness is available (whether delayd due to
11    // DKG, or skipped commits).
12    Randomness {
13        deferred_from_round: CommitRound,
14    },
15    // ConsensusRound deferral key requires both the round to which the tx should be deferred (so
16    // that we can efficiently load all txns that are now ready), and the round from which it
17    // has been deferred (so that multiple rounds can efficiently defer to the same future
18    // round).
19    ConsensusRound {
20        future_round: CommitRound,
21        deferred_from_round: CommitRound,
22    },
23}
24
25impl DeferralKey {
26    pub fn new_for_randomness(deferred_from_round: CommitRound) -> Self {
27        Self::Randomness {
28            deferred_from_round,
29        }
30    }
31
32    pub fn new_for_consensus_round(
33        future_round: CommitRound,
34        deferred_from_round: CommitRound,
35    ) -> Self {
36        Self::ConsensusRound {
37            future_round,
38            deferred_from_round,
39        }
40    }
41
42    pub fn full_range_for_randomness() -> (Self, Self) {
43        (
44            Self::Randomness {
45                deferred_from_round: 0,
46            },
47            Self::Randomness {
48                deferred_from_round: u64::MAX,
49            },
50        )
51    }
52
53    // Returns a range of deferral keys that are deferred up to the given consensus
54    // round.
55    pub fn range_for_up_to_consensus_round(consensus_round: CommitRound) -> (Self, Self) {
56        (
57            Self::ConsensusRound {
58                future_round: 0,
59                deferred_from_round: 0,
60            },
61            Self::ConsensusRound {
62                future_round: consensus_round.checked_add(1).unwrap(),
63                deferred_from_round: 0,
64            },
65        )
66    }
67
68    pub fn deferred_from_round(&self) -> CommitRound {
69        match self {
70            Self::Randomness {
71                deferred_from_round,
72            } => *deferred_from_round,
73            Self::ConsensusRound {
74                deferred_from_round,
75                ..
76            } => *deferred_from_round,
77        }
78    }
79}
80
81#[derive(Debug)]
82pub enum DeferralReason {
83    RandomnessNotReady,
84
85    // The list of objects are congested objects.
86    SharedObjectCongestion(Vec<ObjectID>),
87}
88
89pub fn transaction_deferral_within_limit(
90    deferral_key: &DeferralKey,
91    max_deferral_rounds_for_congestion_control: u64,
92) -> bool {
93    if let DeferralKey::ConsensusRound {
94        future_round,
95        deferred_from_round,
96    } = deferral_key
97    {
98        return (future_round - deferred_from_round) <= max_deferral_rounds_for_congestion_control;
99    }
100
101    // TODO: drop transactions at the end of the queue if the queue is too long.
102
103    true
104}
105
106#[cfg(test)]
107mod object_cost_tests {
108    use typed_store::{
109        DBMapUtils, Map,
110        rocks::{DBMap, MetricConf},
111        traits::{TableSummary, TypedStoreDebug},
112    };
113
114    use super::*;
115
116    #[tokio::test]
117    async fn test_deferral_key_sort_order() {
118        use rand::prelude::*;
119
120        #[derive(DBMapUtils)]
121        struct TestDB {
122            deferred_certs: DBMap<DeferralKey, ()>,
123        }
124
125        // get a tempdir
126        let tempdir = tempfile::tempdir().unwrap();
127
128        let db = TestDB::open_tables_read_write(
129            tempdir.path().to_owned(),
130            MetricConf::new("test_db"),
131            None,
132            None,
133        );
134
135        for _ in 0..10000 {
136            let future_round = rand::thread_rng().gen_range(0..u64::MAX);
137            let current_round = rand::thread_rng().gen_range(0..u64::MAX);
138
139            let key = DeferralKey::new_for_consensus_round(future_round, current_round);
140            db.deferred_certs.insert(&key, &()).unwrap();
141        }
142
143        let mut previous_future_round = 0;
144        for (key, _) in db.deferred_certs.unbounded_iter() {
145            match key {
146                DeferralKey::Randomness { .. } => (),
147                DeferralKey::ConsensusRound { future_round, .. } => {
148                    assert!(previous_future_round <= future_round);
149                    previous_future_round = future_round;
150                }
151            }
152        }
153    }
154
155    // Tests that fetching deferred transactions up to a given consensus rounds
156    // works as expected.
157    #[tokio::test]
158    async fn test_fetching_deferred_txs() {
159        use rand::prelude::*;
160
161        #[derive(DBMapUtils)]
162        struct TestDB {
163            deferred_certs: DBMap<DeferralKey, ()>,
164        }
165
166        // get a tempdir
167        let tempdir = tempfile::tempdir().unwrap();
168
169        let db = TestDB::open_tables_read_write(
170            tempdir.path().to_owned(),
171            MetricConf::new("test_db"),
172            None,
173            None,
174        );
175
176        // All future rounds are between 100 and 300.
177        let min_future_round = 100;
178        let max_future_round = 300;
179        for _ in 0..10000 {
180            let future_round = rand::thread_rng().gen_range(min_future_round..=max_future_round);
181            let current_round = rand::thread_rng().gen_range(0..u64::MAX);
182
183            db.deferred_certs
184                .insert(
185                    &DeferralKey::new_for_consensus_round(future_round, current_round),
186                    &(),
187                )
188                .unwrap();
189            // Add a randomness deferral txn to make sure that it won't show up when
190            // fetching deferred consensus round txs.
191            db.deferred_certs
192                .insert(&DeferralKey::new_for_randomness(current_round), &())
193                .unwrap();
194        }
195
196        // Fetch all deferred transactions up to consensus round 200.
197        let (min, max) = DeferralKey::range_for_up_to_consensus_round(200);
198        let mut previous_future_round = 0;
199        let mut result_count = 0;
200        for result in db
201            .deferred_certs
202            .safe_iter_with_bounds(Some(min), Some(max))
203        {
204            let (key, _) = result.unwrap();
205            match key {
206                DeferralKey::Randomness { .. } => {
207                    panic!("Should not receive randomness deferral txn.")
208                }
209                DeferralKey::ConsensusRound { future_round, .. } => {
210                    assert!(previous_future_round <= future_round);
211                    previous_future_round = future_round;
212                    assert!(future_round <= 200);
213                    result_count += 1;
214                }
215            }
216        }
217        assert!(result_count > 0);
218    }
219}