hotmint_consensus/
commit.rs1use ruc::*;
2
3use crate::application::Application;
4use crate::store::BlockStore;
5use hotmint_types::{Block, BlockHash, DoubleCertificate, Height};
6use tracing::info;
7
8pub fn try_commit(
11 double_cert: &DoubleCertificate,
12 store: &dyn BlockStore,
13 app: &dyn Application,
14 last_committed_height: &mut Height,
15) -> Result<Vec<Block>> {
16 let commit_hash = double_cert.inner_qc.block_hash;
17 let commit_block = store
18 .get_block(&commit_hash)
19 .c(d!("block to commit not found"))?;
20
21 if commit_block.height <= *last_committed_height {
22 return Ok(vec![]);
23 }
24
25 let mut to_commit = Vec::new();
27 let mut current = commit_block;
28 loop {
29 if current.height <= *last_committed_height {
30 break;
31 }
32 let parent_hash = current.parent_hash;
33 to_commit.push(current);
34 if parent_hash == BlockHash::GENESIS {
35 break;
36 }
37 match store.get_block(&parent_hash) {
38 Some(parent) => current = parent,
39 None => break,
40 }
41 }
42
43 to_commit.reverse();
45
46 for block in &to_commit {
47 info!(height = block.height.as_u64(), hash = %block.hash, "committing block");
48 app.on_commit(block).c(d!("application commit failed"))?;
49 *last_committed_height = block.height;
50 }
51
52 Ok(to_commit)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::application::NoopApplication;
59 use crate::store::MemoryBlockStore;
60 use hotmint_types::{AggregateSignature, QuorumCertificate, ValidatorId, ViewNumber};
61
62 fn make_block(height: u64, parent: BlockHash) -> Block {
63 let hash = BlockHash([height as u8; 32]);
64 Block {
65 height: Height(height),
66 parent_hash: parent,
67 view: ViewNumber(height),
68 proposer: ValidatorId(0),
69 payload: vec![],
70 hash,
71 }
72 }
73
74 fn make_qc(hash: BlockHash, view: u64) -> QuorumCertificate {
75 QuorumCertificate {
76 block_hash: hash,
77 view: ViewNumber(view),
78 aggregate_signature: AggregateSignature::new(4),
79 }
80 }
81
82 #[test]
83 fn test_commit_single_block() {
84 let mut store = MemoryBlockStore::new();
85 let app = NoopApplication;
86 let b1 = make_block(1, BlockHash::GENESIS);
87 store.put_block(b1.clone());
88
89 let dc = DoubleCertificate {
90 inner_qc: make_qc(b1.hash, 1),
91 outer_qc: make_qc(b1.hash, 1),
92 };
93
94 let mut last = Height::GENESIS;
95 let committed = try_commit(&dc, &store, &app, &mut last).unwrap();
96 assert_eq!(committed.len(), 1);
97 assert_eq!(committed[0].height, Height(1));
98 assert_eq!(last, Height(1));
99 }
100
101 #[test]
102 fn test_commit_chain_of_blocks() {
103 let mut store = MemoryBlockStore::new();
104 let app = NoopApplication;
105 let b1 = make_block(1, BlockHash::GENESIS);
106 let b2 = make_block(2, b1.hash);
107 let b3 = make_block(3, b2.hash);
108 store.put_block(b1);
109 store.put_block(b2);
110 store.put_block(b3.clone());
111
112 let dc = DoubleCertificate {
113 inner_qc: make_qc(b3.hash, 3),
114 outer_qc: make_qc(b3.hash, 3),
115 };
116
117 let mut last = Height::GENESIS;
118 let committed = try_commit(&dc, &store, &app, &mut last).unwrap();
119 assert_eq!(committed.len(), 3);
120 assert_eq!(committed[0].height, Height(1));
121 assert_eq!(committed[1].height, Height(2));
122 assert_eq!(committed[2].height, Height(3));
123 assert_eq!(last, Height(3));
124 }
125
126 #[test]
127 fn test_commit_already_committed() {
128 let mut store = MemoryBlockStore::new();
129 let app = NoopApplication;
130 let b1 = make_block(1, BlockHash::GENESIS);
131 store.put_block(b1.clone());
132
133 let dc = DoubleCertificate {
134 inner_qc: make_qc(b1.hash, 1),
135 outer_qc: make_qc(b1.hash, 1),
136 };
137
138 let mut last = Height(1); let committed = try_commit(&dc, &store, &app, &mut last).unwrap();
140 assert!(committed.is_empty());
141 }
142
143 #[test]
144 fn test_commit_partial_chain() {
145 let mut store = MemoryBlockStore::new();
146 let app = NoopApplication;
147 let b1 = make_block(1, BlockHash::GENESIS);
148 let b2 = make_block(2, b1.hash);
149 let b3 = make_block(3, b2.hash);
150 store.put_block(b1);
151 store.put_block(b2);
152 store.put_block(b3.clone());
153
154 let dc = DoubleCertificate {
155 inner_qc: make_qc(b3.hash, 3),
156 outer_qc: make_qc(b3.hash, 3),
157 };
158
159 let mut last = Height(1); let committed = try_commit(&dc, &store, &app, &mut last).unwrap();
161 assert_eq!(committed.len(), 2); assert_eq!(committed[0].height, Height(2));
163 assert_eq!(committed[1].height, Height(3));
164 }
165
166 #[test]
167 fn test_commit_missing_block() {
168 let store = MemoryBlockStore::new();
169 let app = NoopApplication;
170 let dc = DoubleCertificate {
171 inner_qc: make_qc(BlockHash([99u8; 32]), 1),
172 outer_qc: make_qc(BlockHash([99u8; 32]), 1),
173 };
174 let mut last = Height::GENESIS;
175 assert!(try_commit(&dc, &store, &app, &mut last).is_err());
176 }
177}