1use std::collections::{BTreeSet, HashMap, HashSet};
20
21use darkfi_sdk::{crypto::MerkleTree, tx::TransactionHash};
22use darkfi_serial::{async_trait, deserialize, SerialDecodable, SerialEncodable};
23use num_bigint::BigUint;
24use sled_overlay::{database::SledDbOverlayStateDiff, sled::IVec};
25use tracing::{debug, info, warn};
26
27use crate::{
28 blockchain::{
29 block_store::{BlockDifficulty, BlockRanks},
30 BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header, HeaderHash,
31 },
32 runtime::vm_runtime::GAS_LIMIT,
33 tx::{Transaction, MAX_TX_CALLS},
34 validator::{
35 pow::{PoWModule, RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
36 utils::{best_fork_index, block_rank, find_extended_fork_index, worst_fork_index},
37 verification::{verify_proposal, verify_transaction},
38 },
39 zk::VerifyingKey,
40 Error, Result,
41};
42
43pub const BLOCK_GAS_LIMIT: u64 = GAS_LIMIT * MAX_TX_CALLS as u64 * 50;
45
46pub struct Consensus {
49 pub blockchain: Blockchain,
51 pub confirmation_threshold: usize,
53 pub forks: Vec<Fork>,
55 max_forks: usize,
57 pub module: PoWModule,
59}
60
61impl Consensus {
62 pub fn new(
64 blockchain: Blockchain,
65 confirmation_threshold: usize,
66 max_forks: usize,
67 pow_target: u32,
68 pow_fixed_difficulty: Option<BigUint>,
69 ) -> Result<Self> {
70 let max_forks = if max_forks == 0 { 1 } else { max_forks };
71 let module = PoWModule::new(blockchain.clone(), pow_target, pow_fixed_difficulty, None)?;
72
73 Ok(Self { blockchain, confirmation_threshold, forks: vec![], max_forks, module })
74 }
75
76 pub async fn generate_empty_fork(&mut self) -> Result<()> {
80 debug!(target: "validator::consensus::generate_empty_fork", "Generating new empty fork...");
81 for fork in &self.forks {
83 if fork.proposals.is_empty() {
84 debug!(target: "validator::consensus::generate_empty_fork", "An empty fork already exists.");
85 return Ok(())
86 }
87 }
88 let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
89 self.push_fork(fork);
90 debug!(target: "validator::consensus::generate_empty_fork", "Fork generated!");
91
92 Ok(())
93 }
94
95 fn push_fork(&mut self, fork: Fork) {
100 if self.forks.len() < self.max_forks {
102 self.forks.push(fork);
103 return
104 }
105
106 let index = worst_fork_index(&self.forks).unwrap();
111
112 if fork.targets_rank < self.forks[index].targets_rank {
114 return
115 }
116
117 if fork.targets_rank == self.forks[index].targets_rank &&
119 fork.hashes_rank <= self.forks[index].hashes_rank
120 {
121 return
122 }
123
124 self.forks[index] = fork;
126 }
127
128 pub async fn append_proposal(
132 &mut self,
133 proposal: &Proposal,
134 is_new: bool,
135 verify_fees: bool,
136 ) -> Result<()> {
137 debug!(target: "validator::consensus::append_proposal", "Appending proposal {}", proposal.hash);
138
139 for fork in &self.forks {
141 for p in fork.proposals.iter().rev() {
142 if p == &proposal.hash {
143 debug!(target: "validator::consensus::append_proposal", "Proposal {} already exists", proposal.hash);
144 return Err(Error::ProposalAlreadyExists)
145 }
146 }
147 }
148 if let Ok(canonical_headers) =
150 self.blockchain.blocks.get_order(&[proposal.block.header.height], true)
151 {
152 if canonical_headers[0].unwrap() == proposal.hash {
153 debug!(target: "validator::consensus::append_proposal", "Proposal {} already exists", proposal.hash);
154 return Err(Error::ProposalAlreadyExists)
155 }
156 }
157
158 let (mut fork, index) = verify_proposal(self, proposal, is_new, verify_fees).await?;
160
161 fork.append_proposal(proposal).await?;
163
164 match index {
167 Some(i) => {
168 if i < self.forks.len() &&
169 self.forks[i].proposals == fork.proposals[..fork.proposals.len() - 1]
170 {
171 self.forks[i] = fork;
172 } else {
173 self.push_fork(fork);
174 }
175 }
176 None => {
177 self.push_fork(fork);
178 }
179 }
180
181 info!(target: "validator::consensus::append_proposal", "Appended proposal {} - {}", proposal.hash, proposal.block.header.height);
182
183 Ok(())
184 }
185
186 pub async fn find_extended_fork(&self, proposal: &Proposal) -> Result<(Fork, Option<usize>)> {
193 let found = find_extended_fork_index(&self.forks, proposal);
195 if found.is_err() {
196 if let Err(Error::ProposalAlreadyExists) = found {
197 return Err(Error::ProposalAlreadyExists)
198 }
199
200 let (last_height, last_block) = self.blockchain.last()?;
202 if proposal.block.header.previous != last_block ||
203 proposal.block.header.height <= last_height
204 {
205 return Err(Error::ExtendedChainIndexNotFound)
206 }
207
208 for (f_index, fork) in self.forks.iter().enumerate() {
210 if fork.proposals.is_empty() {
211 return Ok((self.forks[f_index].full_clone()?, Some(f_index)))
212 }
213 }
214
215 let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
217 return Ok((fork, None))
218 }
219
220 let (f_index, p_index) = found.unwrap();
221 let original_fork = &self.forks[f_index];
222 if p_index == (original_fork.proposals.len() - 1) {
224 return Ok((original_fork.full_clone()?, Some(f_index)))
225 }
226
227 let mut fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
229 fork.proposals = original_fork.proposals[..p_index + 1].to_vec();
230 fork.diffs = original_fork.diffs[..p_index + 1].to_vec();
231
232 let blocks = &original_fork.overlay.lock().unwrap().get_blocks_by_hash(&fork.proposals)?;
234 for (index, block) in blocks.iter().enumerate() {
235 fork.overlay.lock().unwrap().overlay.lock().unwrap().add_diff(&fork.diffs[index])?;
237
238 let (next_target, next_difficulty) = fork.module.next_mine_target_and_difficulty()?;
240
241 let (target_distance_sq, hash_distance_sq) = block_rank(block, &next_target)?;
243
244 fork.module.append(&block.header, &next_difficulty)?;
246
247 fork.targets_rank += target_distance_sq;
249 fork.hashes_rank += hash_distance_sq;
250 }
251
252 Ok((fork, None))
253 }
254
255 pub async fn confirmation(&self) -> Result<Option<usize>> {
265 debug!(target: "validator::consensus::confirmation", "Started confirmation check");
266
267 let index = best_fork_index(&self.forks)?;
269
270 if self.forks[index].proposals.len() < self.confirmation_threshold {
272 debug!(target: "validator::consensus::confirmation", "Nothing to confirm yet, best fork size: {}", self.forks[index].proposals.len());
273 return Ok(None)
274 }
275
276 for (f_index, fork) in self.forks.iter().enumerate() {
278 if f_index == index {
280 continue
281 }
282
283 if fork.targets_rank != self.forks[index].targets_rank {
285 continue
286 }
287
288 if fork.hashes_rank == self.forks[index].hashes_rank {
290 debug!(target: "validator::consensus::confirmation", "Competing best forks found");
291 return Ok(None)
292 }
293 }
294
295 Ok(Some(index))
296 }
297
298 fn find_fork_by_header(&self, fork_header: &HeaderHash) -> Option<usize> {
301 for (index, fork) in self.forks.iter().enumerate() {
302 for p in fork.proposals.iter().rev() {
303 if p == fork_header {
304 return Some(index)
305 }
306 }
307 }
308 None
309 }
310
311 pub async fn get_fork_header_hash(
314 &self,
315 height: u32,
316 fork_header: &HeaderHash,
317 ) -> Result<Option<HeaderHash>> {
318 let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(None) };
320
321 let header =
323 self.forks[index].overlay.lock().unwrap().blocks.get_order(&[height], false)?[0];
324
325 Ok(header)
326 }
327
328 pub async fn get_fork_headers(
332 &self,
333 headers: &[HeaderHash],
334 fork_header: &HeaderHash,
335 ) -> Result<Vec<Header>> {
336 let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(vec![]) };
338
339 let headers = self.forks[index].overlay.lock().unwrap().get_headers_by_hash(headers)?;
341
342 Ok(headers)
343 }
344
345 pub async fn get_fork_proposals(
349 &self,
350 headers: &[HeaderHash],
351 fork_header: &HeaderHash,
352 ) -> Result<Vec<Proposal>> {
353 let Some(index) = self.find_fork_by_header(fork_header) else { return Ok(vec![]) };
355
356 let blocks = self.forks[index].overlay.lock().unwrap().get_blocks_by_hash(headers)?;
358 let mut proposals = Vec::with_capacity(blocks.len());
359 for block in blocks {
360 proposals.push(Proposal::new(block));
361 }
362
363 Ok(proposals)
364 }
365
366 pub async fn get_fork_proposals_after(
372 &self,
373 tip: HeaderHash,
374 fork_tip: Option<HeaderHash>,
375 limit: u32,
376 ) -> Result<Vec<Proposal>> {
377 let mut proposals = vec![];
379
380 let index = match fork_tip {
382 Some(fork_tip) => {
383 let Some(found) = self.find_fork_by_header(&fork_tip) else { return Ok(proposals) };
384 found
385 }
386 None => best_fork_index(&self.forks)?,
387 };
388
389 let Ok(existing_tips) =
391 self.forks[index].overlay.lock().unwrap().get_blocks_by_hash(&[tip])
392 else {
393 return Ok(proposals)
394 };
395
396 let last_block_height = self.forks[index].overlay.lock().unwrap().last()?.0;
398 if last_block_height.saturating_sub(existing_tips[0].header.height) >= limit {
399 return Ok(proposals)
400 }
401
402 let headers = self.blockchain.blocks.get_all_after(existing_tips[0].header.height)?;
404 let blocks = self.blockchain.get_blocks_by_hash(&headers)?;
405 for block in blocks {
406 proposals.push(Proposal::new(block));
407 }
408 let blocks = self.forks[index]
409 .overlay
410 .lock()
411 .unwrap()
412 .get_blocks_by_hash(&self.forks[index].proposals)?;
413 for block in blocks {
414 if block.header.height > existing_tips[0].header.height {
416 proposals.push(Proposal::new(block));
417 }
418 }
419
420 Ok(proposals)
421 }
422
423 pub async fn current_mining_randomx_key(&self) -> Result<HeaderHash> {
427 let (next_block_height, rx_keys) = if self.forks.is_empty() {
430 let (next_block_height, _) = self.blockchain.last()?;
431 (next_block_height + 1, self.module.darkfi_rx_keys)
432 } else {
433 let index = best_fork_index(&self.forks)?;
435 let fork = &self.forks[index];
436 let last = fork.last_proposal()?;
437 (last.block.header.height + 1, fork.module.darkfi_rx_keys)
438 };
439
440 if next_block_height > RANDOMX_KEY_CHANGING_HEIGHT &&
443 next_block_height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
444 {
445 Ok(rx_keys.1.ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?)
446 } else {
447 Ok(rx_keys.0)
448 }
449 }
450
451 pub async fn best_current_fork(&self) -> Result<Fork> {
453 let index = best_fork_index(&self.forks)?;
454 self.forks[index].full_clone()
455 }
456
457 pub async fn best_fork_last_header(&self) -> Result<(u32, HeaderHash)> {
460 if self.forks.is_empty() {
462 return self.blockchain.last()
463 }
464
465 let index = best_fork_index(&self.forks)?;
467 let fork = &self.forks[index];
468
469 let last = fork.last_proposal()?;
471 Ok((last.block.header.height, last.hash))
472 }
473
474 pub async fn reset_forks(
484 &mut self,
485 prefix: &[HeaderHash],
486 confirmed_fork_index: &usize,
487 confirmed_txs: &[Transaction],
488 ) -> Result<()> {
489 let excess = prefix.len();
494 let prefix_last_index = excess - 1;
495 let prefix_last = prefix.last().unwrap();
496 let mut keep = vec![true; self.forks.len()];
497 let confirmed_txs_hashes: Vec<TransactionHash> =
498 confirmed_txs.iter().map(|tx| tx.hash()).collect();
499 for (index, fork) in self.forks.iter_mut().enumerate() {
500 if &index == confirmed_fork_index {
501 fork.mempool.retain(|tx| !confirmed_txs_hashes.contains(tx));
503 continue
504 }
505
506 if fork.proposals.is_empty() ||
512 prefix_last_index >= fork.proposals.len() ||
513 &fork.proposals[prefix_last_index] != prefix_last
514 {
515 keep[index] = false;
516 continue
517 }
518
519 fork.mempool.retain(|tx| !confirmed_txs_hashes.contains(tx));
521
522 let rest_proposals = fork.proposals.split_off(excess);
524 let rest_diffs = fork.diffs.split_off(excess);
525 let mut diffs = fork.diffs.clone();
526 fork.proposals = rest_proposals;
527 fork.diffs = rest_diffs;
528 for diff in diffs.iter_mut() {
529 fork.overlay.lock().unwrap().overlay.lock().unwrap().remove_diff(diff);
530 }
531 }
532
533 let mut iter = keep.iter();
535 self.forks.retain(|_| *iter.next().unwrap());
536
537 self.blockchain.remove_pending_txs_hashes(&confirmed_txs_hashes)?;
540
541 Ok(())
542 }
543
544 pub async fn purge_forks(&mut self) -> Result<()> {
547 debug!(target: "validator::consensus::purge_forks", "Purging current forks...");
548 self.forks = vec![Fork::new(self.blockchain.clone(), self.module.clone()).await?];
549 debug!(target: "validator::consensus::purge_forks", "Forks purged!");
550
551 Ok(())
552 }
553
554 pub async fn reset_pow_module(&mut self) -> Result<()> {
556 debug!(target: "validator::consensus::reset_pow_module", "Resetting PoW module...");
557 self.module = PoWModule::new(
558 self.blockchain.clone(),
559 self.module.target,
560 self.module.fixed_difficulty.clone(),
561 None,
562 )?;
563 debug!(target: "validator::consensus::reset_pow_module", "PoW module reset successfully!");
564
565 Ok(())
566 }
567
568 pub async fn healthcheck(&self) -> Result<()> {
571 let state_root = self.blockchain.contracts.get_state_monotree_root()?;
573
574 let last_block_state_root = self.blockchain.last_header()?.state_root;
576 if state_root != last_block_state_root {
577 return Err(Error::ContractsStatesRootError(
578 blake3::Hash::from_bytes(state_root).to_string(),
579 blake3::Hash::from_bytes(last_block_state_root).to_string(),
580 ));
581 }
582
583 for fork in &self.forks {
585 fork.healthcheck()?;
586 }
587
588 Ok(())
589 }
590
591 pub async fn purge_unreferenced_trees(
594 &self,
595 referenced_trees: &mut BTreeSet<IVec>,
596 ) -> Result<()> {
597 if self.forks.is_empty() {
599 let fork = Fork::new(self.blockchain.clone(), self.module.clone()).await?;
602 fork.referenced_trees(referenced_trees);
603 } else {
604 for fork in &self.forks {
606 fork.referenced_trees(referenced_trees);
607 }
608 }
609
610 let current_trees = self.blockchain.sled_db.tree_names();
612
613 for tree in current_trees {
616 if referenced_trees.contains(&tree) {
618 continue
619 }
620
621 let Ok(tree) = deserialize::<[u8; 32]>(&tree) else { continue };
623
624 debug!(target: "validator::consensus::purge_unreferenced_trees", "Dropping unreferenced tree: {}", blake3::Hash::from(tree));
626 self.blockchain.sled_db.drop_tree(tree)?;
627 }
628
629 Ok(())
630 }
631
632 pub async fn purge_unproposed_pending_txs(
635 &mut self,
636 mut proposed_txs: HashSet<TransactionHash>,
637 ) -> Result<()> {
638 for fork in &self.forks {
640 let proposals_txs =
642 fork.overlay.lock().unwrap().get_blocks_txs_hashes(&fork.proposals)?;
643 for tx in proposals_txs {
644 proposed_txs.insert(tx);
645 }
646 }
647
648 for fork in self.forks.iter_mut() {
651 fork.mempool.retain(|tx| proposed_txs.contains(tx));
652 }
653
654 let proposed_txs: Vec<TransactionHash> = proposed_txs.into_iter().collect();
656 self.blockchain.reset_pending_txs(&proposed_txs)?;
657
658 Ok(())
659 }
660}
661
662#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
664pub struct Proposal {
665 pub hash: HeaderHash,
667 pub block: BlockInfo,
669}
670
671impl Proposal {
672 pub fn new(block: BlockInfo) -> Self {
673 let hash = block.hash();
674 Self { hash, block }
675 }
676}
677
678impl From<Proposal> for BlockInfo {
679 fn from(proposal: Proposal) -> BlockInfo {
680 proposal.block
681 }
682}
683
684#[derive(Clone)]
691pub struct Fork {
692 pub blockchain: Blockchain,
694 pub overlay: BlockchainOverlayPtr,
696 pub module: PoWModule,
698 pub proposals: Vec<HeaderHash>,
700 pub diffs: Vec<SledDbOverlayStateDiff>,
702 pub mempool: Vec<TransactionHash>,
704 pub targets_rank: BigUint,
706 pub hashes_rank: BigUint,
708}
709
710impl Fork {
711 pub async fn new(blockchain: Blockchain, module: PoWModule) -> Result<Self> {
712 let mempool = blockchain.get_pending_txs()?.iter().map(|tx| tx.hash()).collect();
713 let overlay = BlockchainOverlay::new(&blockchain)?;
714 let last_difficulty = blockchain.last_block_difficulty()?;
716 let targets_rank = last_difficulty.ranks.targets_rank;
717 let hashes_rank = last_difficulty.ranks.hashes_rank;
718 Ok(Self {
719 blockchain,
720 overlay,
721 module,
722 proposals: vec![],
723 diffs: vec![],
724 mempool,
725 targets_rank,
726 hashes_rank,
727 })
728 }
729
730 pub async fn append_proposal(&mut self, proposal: &Proposal) -> Result<()> {
733 let (next_target, next_difficulty) = self.module.next_mine_target_and_difficulty()?;
735
736 let (target_distance_sq, hash_distance_sq) = block_rank(&proposal.block, &next_target)?;
738
739 self.targets_rank += target_distance_sq.clone();
741 self.hashes_rank += hash_distance_sq.clone();
742
743 let cumulative_difficulty =
745 self.module.cumulative_difficulty.clone() + next_difficulty.clone();
746 let ranks = BlockRanks::new(
747 target_distance_sq,
748 self.targets_rank.clone(),
749 hash_distance_sq,
750 self.hashes_rank.clone(),
751 );
752 let block_difficulty = BlockDifficulty::new(
753 proposal.block.header.height,
754 proposal.block.header.timestamp,
755 next_difficulty,
756 cumulative_difficulty,
757 ranks,
758 );
759 self.module.append_difficulty(&self.overlay, &proposal.block.header, block_difficulty)?;
760
761 self.proposals.push(proposal.hash);
763
764 self.diffs.push(self.overlay.lock().unwrap().overlay.lock().unwrap().diff(&self.diffs)?);
766
767 Ok(())
768 }
769
770 pub fn last_proposal(&self) -> Result<Proposal> {
772 let block = if let Some(last) = self.proposals.last() {
773 self.overlay.lock().unwrap().get_blocks_by_hash(&[*last])?[0].clone()
774 } else {
775 self.overlay.lock().unwrap().last_block()?
776 };
777
778 Ok(Proposal::new(block))
779 }
780
781 pub fn get_next_block_height(&self) -> Result<u32> {
783 let proposal = self.last_proposal()?;
784 Ok(proposal.block.header.height + 1)
785 }
786
787 pub async fn unproposed_txs(
793 &mut self,
794 verifying_block_height: u32,
795 verify_fees: bool,
796 ) -> Result<(Vec<Transaction>, u64, u64)> {
797 if self.mempool.is_empty() {
799 return Ok((vec![], 0, 0))
800 }
801
802 let mut tree = MerkleTree::new(1);
804
805 let mut total_gas_used = 0_u64;
807 let mut total_gas_paid = 0_u64;
808
809 let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
812
813 let proposals_txs = self.overlay.lock().unwrap().get_blocks_txs_hashes(&self.proposals)?;
815
816 let mut unproposed_txs = vec![];
819 let mut erroneous_txs = vec![];
820 for tx in &self.mempool {
821 if proposals_txs.contains(tx) {
824 continue
825 }
826
827 let unproposed_tx = match self.blockchain.transactions.get_pending(&[*tx], true) {
829 Ok(txs) => txs[0].clone().unwrap(),
830 Err(e) => {
831 debug!(target: "validator::consensus::unproposed_txs", "Transaction retrieval failed: {e}");
832 erroneous_txs.push(*tx);
833 continue
834 }
835 };
836
837 for call in &unproposed_tx.calls {
839 vks.entry(call.data.contract_id.to_bytes()).or_default();
840 }
841
842 self.overlay.lock().unwrap().checkpoint();
844 let gas_data = match verify_transaction(
845 &self.overlay,
846 verifying_block_height,
847 self.module.target,
848 &unproposed_tx,
849 &mut tree,
850 &mut vks,
851 verify_fees,
852 )
853 .await
854 {
855 Ok(gas_values) => gas_values,
856 Err(e) => {
857 debug!(target: "validator::consensus::unproposed_txs", "Transaction verification failed: {e}");
858 self.overlay.lock().unwrap().revert_to_checkpoint();
859 erroneous_txs.push(*tx);
860 continue
861 }
862 };
863
864 let tx_gas_used = gas_data.total_gas_used();
866
867 let accumulated_gas_usage = total_gas_used.saturating_add(tx_gas_used);
869
870 if accumulated_gas_usage > BLOCK_GAS_LIMIT {
873 warn!(
874 target: "validator::consensus::unproposed_txs",
875 "Retrieving transaction {tx} would exceed configured unproposed transaction gas limit: {accumulated_gas_usage} - {BLOCK_GAS_LIMIT}"
876 );
877 self.overlay.lock().unwrap().revert_to_checkpoint();
878 break
879 }
880
881 total_gas_used = total_gas_used.saturating_add(tx_gas_used);
883 total_gas_paid = total_gas_paid.saturating_add(gas_data.paid);
884
885 unproposed_txs.push(unproposed_tx);
887 }
888
889 self.mempool.retain(|tx| !erroneous_txs.contains(tx));
891
892 Ok((unproposed_txs, total_gas_used, total_gas_paid))
893 }
894
895 pub fn full_clone(&self) -> Result<Self> {
900 let blockchain = self.blockchain.clone();
901 let overlay = self.overlay.lock().unwrap().full_clone()?;
902 let module = self.module.clone();
903 let proposals = self.proposals.clone();
904 let diffs = self.diffs.clone();
905 let mempool = self.mempool.clone();
906 let targets_rank = self.targets_rank.clone();
907 let hashes_rank = self.hashes_rank.clone();
908
909 Ok(Self {
910 blockchain,
911 overlay,
912 module,
913 proposals,
914 diffs,
915 mempool,
916 targets_rank,
917 hashes_rank,
918 })
919 }
920
921 pub fn healthcheck(&self) -> Result<()> {
928 let state_root = self.overlay.lock().unwrap().contracts.get_state_monotree_root()?;
930
931 let last_block_state_root = self.last_proposal()?.block.header.state_root;
933 if state_root != last_block_state_root {
934 return Err(Error::ContractsStatesRootError(
935 blake3::Hash::from_bytes(state_root).to_string(),
936 blake3::Hash::from_bytes(last_block_state_root).to_string(),
937 ));
938 }
939
940 Ok(())
941 }
942
943 pub fn referenced_trees(&self, trees: &mut BTreeSet<IVec>) {
946 let fork_overlay = self.overlay.lock().unwrap();
948 let overlay = fork_overlay.overlay.lock().unwrap();
949
950 for initial_tree in &overlay.state.initial_tree_names {
952 trees.insert(initial_tree.clone());
953 }
954
955 for new_tree in &overlay.state.new_tree_names {
957 trees.insert(new_tree.clone());
958 }
959
960 for dropped_tree in overlay.state.dropped_trees.keys() {
962 trees.insert(dropped_tree.clone());
963 }
964
965 for protected_tree in &overlay.state.protected_tree_names {
967 trees.insert(protected_tree.clone());
968 }
969 }
970}