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}