cuprate_consensus_context/
hardforks.rs1use std::ops::Range;
2
3use tower::ServiceExt;
4use tracing::instrument;
5
6use cuprate_consensus_rules::{HFVotes, HFsInfo, HardFork};
7use cuprate_types::{
8 blockchain::{BlockchainReadRequest, BlockchainResponse},
9 Chain,
10};
11
12use crate::{ContextCacheError, Database};
13
14const DEFAULT_WINDOW_SIZE: usize = 10080; #[derive(Debug, Clone, Copy, Eq, PartialEq)]
22pub struct HardForkConfig {
23 pub info: HFsInfo,
25 pub window: usize,
27}
28
29impl HardForkConfig {
30 pub const fn main_net() -> Self {
32 Self {
33 info: HFsInfo::main_net(),
34 window: DEFAULT_WINDOW_SIZE,
35 }
36 }
37
38 pub const fn stage_net() -> Self {
40 Self {
41 info: HFsInfo::stage_net(),
42 window: DEFAULT_WINDOW_SIZE,
43 }
44 }
45
46 pub const fn test_net() -> Self {
48 Self {
49 info: HFsInfo::test_net(),
50 window: DEFAULT_WINDOW_SIZE,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Eq, PartialEq)]
57pub struct HardForkState {
58 pub current_hardfork: HardFork,
60
61 pub config: HardForkConfig,
63 pub votes: HFVotes,
65
66 pub last_height: usize,
68}
69
70impl HardForkState {
71 #[instrument(name = "init_hardfork_state", skip(config, database), level = "info")]
73 pub async fn init_from_chain_height<D: Database + Clone>(
74 chain_height: usize,
75 config: HardForkConfig,
76 mut database: D,
77 ) -> Result<Self, ContextCacheError> {
78 tracing::info!("Initializing hard-fork state this may take a while.");
79
80 let block_start = chain_height.saturating_sub(config.window);
81
82 let votes =
83 get_votes_in_range(database.clone(), block_start..chain_height, config.window).await?;
84
85 if chain_height > config.window {
86 debug_assert_eq!(votes.total_votes(), config.window);
87 }
88
89 let BlockchainResponse::BlockExtendedHeader(ext_header) = database
90 .ready()
91 .await?
92 .call(BlockchainReadRequest::BlockExtendedHeader(chain_height - 1))
93 .await?
94 else {
95 panic!("Database sent incorrect response!");
96 };
97
98 let current_hardfork = ext_header.version;
99
100 let mut hfs = Self {
101 config,
102 current_hardfork,
103 votes,
104 last_height: chain_height - 1,
105 };
106
107 hfs.check_set_new_hf();
108
109 tracing::info!(
110 "Initialized Hfs, current fork: {:?}, {}",
111 hfs.current_hardfork,
112 hfs.votes
113 );
114
115 Ok(hfs)
116 }
117
118 pub async fn pop_blocks_main_chain<D: Database + Clone>(
126 &mut self,
127 numb_blocks: usize,
128 database: D,
129 ) -> Result<(), ContextCacheError> {
130 let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else {
131 *self = Self::init_from_chain_height(
132 self.last_height + 1 - numb_blocks,
133 self.config,
134 database,
135 )
136 .await?;
137
138 return Ok(());
139 };
140
141 let current_chain_height = self.last_height + 1;
142
143 let oldest_votes = get_votes_in_range(
144 database,
145 current_chain_height
146 .saturating_sub(self.config.window)
147 .saturating_sub(numb_blocks)
148 ..current_chain_height
149 .saturating_sub(numb_blocks)
150 .saturating_sub(retained_blocks),
151 numb_blocks,
152 )
153 .await?;
154
155 self.votes.reverse_blocks(numb_blocks, oldest_votes);
156 self.last_height -= numb_blocks;
157
158 Ok(())
159 }
160
161 pub fn new_block(&mut self, vote: HardFork, height: usize) {
163 assert_eq!(self.last_height + 1, height);
166 self.last_height += 1;
167
168 tracing::debug!(
169 "Accounting for new blocks vote, height: {}, vote: {:?}",
170 self.last_height,
171 vote
172 );
173
174 self.votes.add_vote_for_hf(&vote);
176
177 if height > self.config.window {
178 debug_assert_eq!(self.votes.total_votes(), self.config.window);
179 }
180
181 self.check_set_new_hf();
182 }
183
184 fn check_set_new_hf(&mut self) {
188 self.current_hardfork = self.votes.current_fork(
189 &self.current_hardfork,
190 self.last_height + 1,
191 self.config.window,
192 &self.config.info,
193 );
194 }
195
196 pub const fn current_hardfork(&self) -> HardFork {
198 self.current_hardfork
199 }
200}
201
202#[instrument(name = "get_votes", skip(database))]
204async fn get_votes_in_range<D: Database>(
205 database: D,
206 block_heights: Range<usize>,
207 window_size: usize,
208) -> Result<HFVotes, ContextCacheError> {
209 let mut votes = HFVotes::new(window_size);
210
211 let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database
212 .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
213 block_heights,
214 Chain::Main,
215 ))
216 .await?
217 else {
218 panic!("Database sent incorrect response!");
219 };
220
221 for hf_info in vote_list {
222 votes.add_vote_for_hf(&HardFork::from_vote(hf_info.vote));
223 }
224
225 Ok(votes)
226}