radix_engine/updates/
protocol_update_generation.rs

1use super::*;
2use crate::internal_prelude::*;
3
4/// Generates batches for the protocol update. These are structured as:
5/// * One or more named batch groups
6/// * One or more batches under each batch group.
7///   Each batch is committed separately in the node. Separating into batches allows the
8///   node not to have to hold too much in memory at any given time.
9///
10/// The batch generation must be stateless (aside from the database), to allow the update
11/// to be resumed in the node mid-way through after a restart.
12///
13/// Therefore any transient state required between batches must be stored in the database,
14/// and we must ensure that whilst each batch group is executing, the content of
15/// the batch is fixed.
16///
17/// The use of lazy Generator traits is designed to allow the content of batch groups /
18/// batches to be resolved lazily (e.g. with input from the database).
19pub trait ProtocolUpdateGenerator: 'static {
20    fn insert_status_tracking_flash_transactions(&self) -> bool {
21        true
22    }
23
24    /// Return the list of batch groups for the protocol update.
25    ///
26    /// Each should be a fixed, conceptual step in the update process.
27    fn batch_groups(&self) -> Vec<Box<dyn ProtocolUpdateBatchGroupGenerator + '_>>;
28}
29
30/// Each batch group is a logical grouping of batches.
31///
32/// For example, at genesis, there are three batch groups:
33/// * `"bootstrap"` (flash + bootstrap transaction)
34/// * `"chunks"`
35/// * `"wrap-up"`
36/// * The node also adds a `"scenarios"` batch group.
37pub trait ProtocolUpdateBatchGroupGenerator<'a> {
38    /// This is `&'static` because batch groups are intended to be fixed conceptual steps
39    /// in the protocol update.
40    ///
41    /// The batch-group name should be kebab-case for consistency.
42    fn batch_group_name(&self) -> &'static str;
43
44    /// The content of these batches must be *fully reproducible* from the state of the store
45    /// *before any updates were committed*. This is why we return an array of batch generators.
46    ///
47    /// If a protocol update needs to do some complicated/inline batch updates to substates, you may need to:
48    /// * Have a first batch group where the planned work is saved batch-by-batch to some special partition
49    /// * Have a second batch group where the planned work is performed, by reading from this special partition
50    /// * Have a third batch group where the planned work is deleted
51    fn generate_batches(
52        self: Box<Self>,
53        store: &dyn SubstateDatabase,
54    ) -> Vec<Box<dyn ProtocolUpdateBatchGenerator + 'a>>;
55}
56
57/// Generate a batch of transactions to be committed atomically with a proof.
58///
59/// It should be assumed that the [`SubstateDatabase`] has *committed all previous batches*.
60/// This ensures that the update is deterministically continuable if the node shuts down
61/// mid-update.
62pub trait ProtocolUpdateBatchGenerator {
63    /// The batch name should be kebab-case for consistency
64    fn batch_name(&self) -> &str;
65
66    /// Generates the content of the batch
67    fn generate_batch(self: Box<Self>, store: &dyn SubstateDatabase) -> ProtocolUpdateBatch;
68}
69
70pub(super) struct NoOpGenerator;
71
72impl ProtocolUpdateGenerator for NoOpGenerator {
73    fn batch_groups(&self) -> Vec<Box<dyn ProtocolUpdateBatchGroupGenerator>> {
74        vec![]
75    }
76}
77
78/// A simple batch group generator, which knows its batches in advance.
79///
80/// For some protocol updates, you might want to use a custom batch group generator,
81/// which is more lazy, or sources its work from the database.
82pub struct FixedBatchGroupGenerator<'a> {
83    name: &'static str,
84    batches: Vec<Box<dyn ProtocolUpdateBatchGenerator + 'a>>,
85}
86
87impl<'a> FixedBatchGroupGenerator<'a> {
88    pub fn named(name: &'static str) -> Self {
89        if name != name.to_ascii_lowercase().as_str() {
90            panic!("Protocol update batch group names should be in kebab-case for consistency");
91        }
92        Self {
93            name,
94            batches: vec![],
95        }
96    }
97
98    pub fn add_bespoke_batch(mut self, batch: impl ProtocolUpdateBatchGenerator + 'a) -> Self {
99        self.batches.push(Box::new(batch));
100        self
101    }
102
103    pub fn add_batch(
104        self,
105        name: impl Into<String>,
106        generator: impl FnOnce(&dyn SubstateDatabase) -> ProtocolUpdateBatch + 'a,
107    ) -> Self {
108        self.add_bespoke_batch(BatchGenerator::new(name, generator))
109    }
110
111    pub fn build(self) -> Box<dyn ProtocolUpdateBatchGroupGenerator<'a> + 'a> {
112        Box::new(self)
113    }
114}
115
116impl<'a> ProtocolUpdateBatchGroupGenerator<'a> for FixedBatchGroupGenerator<'a> {
117    fn batch_group_name(&self) -> &'static str {
118        self.name
119    }
120
121    fn generate_batches(
122        self: Box<Self>,
123        _store: &dyn SubstateDatabase,
124    ) -> Vec<Box<dyn ProtocolUpdateBatchGenerator + 'a>> {
125        self.batches
126    }
127}
128
129pub struct BatchGenerator<'a> {
130    name: String,
131    generator: Box<dyn FnOnce(&dyn SubstateDatabase) -> ProtocolUpdateBatch + 'a>,
132}
133
134impl<'a> BatchGenerator<'a> {
135    pub fn new(
136        name: impl Into<String>,
137        generator: impl FnOnce(&dyn SubstateDatabase) -> ProtocolUpdateBatch + 'a,
138    ) -> Self {
139        let name = name.into();
140        if name.to_ascii_lowercase() != name {
141            panic!("Protocol update batch names should be in kebab-case for consistency");
142        }
143        Self {
144            name,
145            generator: Box::new(generator),
146        }
147    }
148
149    pub fn build(self) -> Box<dyn ProtocolUpdateBatchGenerator + 'a> {
150        Box::new(self)
151    }
152}
153
154impl<'a> ProtocolUpdateBatchGenerator for BatchGenerator<'a> {
155    fn batch_name(&self) -> &str {
156        self.name.as_str()
157    }
158
159    fn generate_batch(self: Box<Self>, store: &dyn SubstateDatabase) -> ProtocolUpdateBatch {
160        (self.generator)(store)
161    }
162}