Skip to main content

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_sdk_types::ObjectId;
6use iota_types::base_types::CommitRound;
7use serde::{Deserialize, Serialize};
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
10pub enum DeferralKey {
11    // For transactions deferred until new randomness is available (whether delayd due to
12    // DKG, or skipped commits).
13    Randomness {
14        deferred_from_round: CommitRound,
15    },
16    // ConsensusRound deferral key requires both the round to which the tx should be deferred (so
17    // that we can efficiently load all txns that are now ready), and the round from which it
18    // has been deferred (so that multiple rounds can efficiently defer to the same future
19    // round).
20    ConsensusRound {
21        future_round: CommitRound,
22        deferred_from_round: CommitRound,
23    },
24}
25
26impl DeferralKey {
27    pub fn new_for_randomness(deferred_from_round: CommitRound) -> Self {
28        Self::Randomness {
29            deferred_from_round,
30        }
31    }
32
33    pub fn new_for_consensus_round(
34        future_round: CommitRound,
35        deferred_from_round: CommitRound,
36    ) -> Self {
37        Self::ConsensusRound {
38            future_round,
39            deferred_from_round,
40        }
41    }
42
43    pub fn full_range_for_randomness() -> (Self, Self) {
44        (
45            Self::Randomness {
46                deferred_from_round: 0,
47            },
48            Self::Randomness {
49                deferred_from_round: u64::MAX,
50            },
51        )
52    }
53
54    // Returns a range of deferral keys that are deferred up to the given consensus
55    // round.
56    pub fn range_for_up_to_consensus_round(consensus_round: CommitRound) -> (Self, Self) {
57        (
58            Self::ConsensusRound {
59                future_round: 0,
60                deferred_from_round: 0,
61            },
62            Self::ConsensusRound {
63                future_round: consensus_round.checked_add(1).unwrap(),
64                deferred_from_round: 0,
65            },
66        )
67    }
68
69    pub fn deferred_from_round(&self) -> CommitRound {
70        match self {
71            Self::Randomness {
72                deferred_from_round,
73            } => *deferred_from_round,
74            Self::ConsensusRound {
75                deferred_from_round,
76                ..
77            } => *deferred_from_round,
78        }
79    }
80}
81
82#[derive(Debug)]
83pub enum DeferralReason {
84    RandomnessNotReady,
85
86    // The list of objects are congested objects.
87    SharedObjectCongestion(Vec<ObjectId>),
88}
89
90pub fn transaction_deferral_within_limit(
91    deferral_key: &DeferralKey,
92    max_deferral_rounds_for_congestion_control: u64,
93) -> bool {
94    if let DeferralKey::ConsensusRound {
95        future_round,
96        deferred_from_round,
97    } = deferral_key
98    {
99        return (future_round - deferred_from_round) <= max_deferral_rounds_for_congestion_control;
100    }
101
102    // TODO: drop transactions at the end of the queue if the queue is too long.
103
104    true
105}
106
107#[cfg(test)]
108mod object_cost_tests {
109    use typed_store::{
110        DBMapUtils, Map,
111        rocks::{DBMap, MetricConf},
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 tmp_dir = iota_common::tempdir();
127
128        let db = TestDB::open_tables_read_write(
129            tmp_dir.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 item in db.deferred_certs.safe_iter() {
145            match item.unwrap().0 {
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 tmp_dir = iota_common::tempdir();
168
169        let db = TestDB::open_tables_read_write(
170            tmp_dir.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}