sui_core/authority/
transaction_deferral.rs

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