1#![allow(clippy::module_inception)]
2pub mod consensus;
3pub mod doms;
4pub mod fabric;
5pub mod genesis;
6
7pub use amadeus_utils::constants::{
9 DST, DST_ANR, DST_ANR_CHALLENGE, DST_ATT, DST_ENTRY, DST_MOTION, DST_NODE, DST_POP, DST_TX, DST_VRF,
10};
11
12use crate::utils::misc::TermExt;
13use crate::utils::rocksdb::RocksDb;
14use amadeus_utils::constants::CF_SYSCONF;
15use eetf::Term;
16
17pub fn chain_epoch(db: &RocksDb) -> u32 {
20 chain_height(db) / 100_000
21}
22
23pub fn chain_height(db: &RocksDb) -> u32 {
25 match db.get(CF_SYSCONF, b"temporal_height") {
26 Ok(Some(bytes)) => {
27 match Term::decode(&bytes[..]) {
29 Ok(term) => TermExt::get_integer(&term).unwrap_or(0) as u32,
30 Err(_) => 0, }
32 }
33 _ => 0, }
35}
36
37#[cfg(test)]
38mod tests {
39 use crate::consensus::consensus::apply_entry;
40 use crate::consensus::fabric::Fabric;
41 use crate::utils::Hash;
42 use eetf::Term;
43 use std::path::Path;
44
45 #[tokio::test]
46 #[ignore = "requires migrated database snapshot with new CF schema"]
47 async fn test_apply_entry_34076357() -> Result<(), Box<dyn std::error::Error>> {
48 let hash = bs58::decode("DEYRMxK3rCgVvwFagmpJQecbreiLUeYjRxrVfs6yKiJ5").into_vec()?;
49 test_apply_entry_at_height(34076356, hash.try_into().map_err(|_| "invalid hash")?).await
50 }
51
52 #[tokio::test]
53 #[ignore = "requires migrated database snapshot with new CF schema"]
54 async fn test_apply_entry_34076383() -> Result<(), Box<dyn std::error::Error>> {
55 let hash = bs58::decode("53NtszVMj5nBA7PnaDsLtiSZAX6T6LvmH74BngSVtp6C").into_vec()?;
56 test_apply_entry_at_height(34076382, hash.try_into().map_err(|_| "invalid hash")?).await
57 }
58
59 #[tokio::test]
60 #[ignore = "requires migrated database snapshot with new CF schema"]
61 async fn test_apply_entry_34076433() -> Result<(), Box<dyn std::error::Error>> {
62 let hash = bs58::decode("12mVLz4waDiBb9qqqnD5KLJMxRvAMaDz6W1pidXA1cm6").into_vec()?;
63 test_apply_entry_at_height(34076432, hash.try_into().map_err(|_| "invalid hash")?).await
64 }
65
66 #[tokio::test]
67 #[ignore = "requires migrated database snapshot with new CF schema"]
68 async fn test_apply_entry_34099999() -> Result<(), Box<dyn std::error::Error>> {
69 let hash = bs58::decode("fR28rtEYFfm6iwWDrJXvQjEBJNT1NZNak52NTwFuiU3").into_vec()?;
70 test_apply_entry_at_height(34099998, hash.try_into().map_err(|_| "invalid hash")?).await
71 }
72
73 async fn test_apply_entry_at_height(
74 height: u32,
75 expected_muts_hash: Hash,
76 ) -> Result<(), Box<dyn std::error::Error>> {
77 let db_path = format!("../assets/rocksdb/{}", height);
78 assert!(Path::new(&db_path).exists(), "Test database snapshot not found: {}", db_path);
79
80 let temp = format!("/tmp/test-rocksdb-{}-{}", height, std::process::id());
82 let temp_db = format!("{}/db/fabric", temp);
83 if Path::new(&temp).exists() {
84 std::fs::remove_dir_all(&temp)?;
85 }
86 copy_dir(&format!("{}/fabric", db_path), &temp_db)?;
87
88 let fabric = Fabric::new(&temp).await?;
90
91 let next_height = height + 1;
93 let entries = fabric.entries_by_height(next_height as u64)?;
94 let entry = crate::consensus::doms::entry::Entry::from_vecpak_bin(&entries[0])?;
95
96 let expected_logs_path = format!("{}/next_logs", db_path);
98 if Path::new(&expected_logs_path).exists() {
99 let expected_logs_bin = std::fs::read(&expected_logs_path)?;
100 println!("\n=== Expected logs from next_logs file ===");
101 match decode_logs(&expected_logs_bin) {
102 Ok(logs) => {
103 for (i, (error, log_list)) in logs.iter().enumerate() {
104 println!("Transaction {}: error={:?}, logs={:?}", i, error, log_list);
105 }
106 }
107 Err(e) => println!("Failed to decode expected logs: {}", e),
108 }
109 } else {
110 println!("\n=== No next_logs file found ===");
111 }
112
113 let config = create_test_config();
115 apply_entry(&fabric, &config, &entry)?;
116
117 let muts = crate::consensus::consensus::chain_muts(&fabric, &entry.hash).ok_or("no muts")?;
119 let muts_rev = crate::consensus::consensus::chain_muts_rev(&fabric, &entry.hash).ok_or("no muts_rev")?;
120
121 let exp_muts = decode_muts(&std::fs::read(format!("{}/next_muts", db_path))?)?;
123 let exp_muts_rev = decode_muts(&std::fs::read(format!("{}/next_muts_rev", db_path))?)?;
124
125 assert_eq!(muts.len(), exp_muts.len(), "muts count");
127 assert_eq!(muts_rev.len(), exp_muts_rev.len(), "muts_rev count");
128
129 for (i, (r, e)) in muts.iter().zip(exp_muts.iter()).enumerate() {
130 assert_eq!(r, e, "muts[{}] mismatch", i);
131 }
132
133 let my_att = fabric.my_attestation_by_entryhash(&*entry.hash)?.ok_or("no attestation")?;
135 assert_eq!(my_att.mutations_hash, expected_muts_hash, "mutations hash mismatch");
136
137 std::fs::remove_dir_all(&temp).ok();
138 Ok(())
139 }
140
141 fn decode_logs(bin: &[u8]) -> Result<Vec<(String, Vec<String>)>, Box<dyn std::error::Error>> {
142 use crate::utils::misc::TermExt;
143 let term = Term::decode(bin)?;
144 let outer_list = match &term {
145 Term::List(l) => l,
146 _ => return Err("not list".into()),
147 };
148
149 outer_list
150 .elements
151 .iter()
152 .map(|e| {
153 let m = match e {
154 Term::Map(m) => &m.map,
155 _ => return Err("not map".into()),
156 };
157
158 let error = match m.get(&Term::Atom(eetf::Atom::from("error"))) {
160 Some(Term::Atom(a)) => a.name.as_str().to_string(),
161 _ => return Err("no error field".into()),
162 };
163
164 let logs = match m.get(&Term::Atom(eetf::Atom::from("logs"))) {
166 Some(Term::List(log_list)) => log_list
167 .elements
168 .iter()
169 .filter_map(|log_term| {
170 log_term.get_binary().map(|bytes| String::from_utf8_lossy(&bytes).to_string())
171 })
172 .collect(),
173 _ => vec![],
174 };
175
176 Ok((error, logs))
177 })
178 .collect()
179 }
180
181 fn decode_muts(
182 bin: &[u8],
183 ) -> Result<Vec<amadeus_runtime::consensus::consensus_muts::Mutation>, Box<dyn std::error::Error>> {
184 use crate::utils::misc::TermExt;
185 let term = Term::decode(bin)?;
186 let list = match &term {
187 Term::List(l) => l,
188 _ => return Err("not list".into()),
189 };
190 list.elements
191 .iter()
192 .map(|e| {
193 let m = match e {
194 Term::Map(m) => &m.map,
195 _ => return Err("not map".into()),
196 };
197 let op = match m.get(&Term::Atom(eetf::Atom::from("op"))) {
198 Some(Term::Atom(a)) => a,
199 _ => return Err("no op".into()),
200 };
201 let key =
202 m.get(&Term::Atom(eetf::Atom::from("key"))).and_then(|t| t.get_binary()).ok_or("no key")?.to_vec();
203
204 match op.name.as_str() {
205 "put" => {
206 let value = m
207 .get(&Term::Atom(eetf::Atom::from("value")))
208 .and_then(|t| t.get_binary())
209 .ok_or("no val")?
210 .to_vec();
211 Ok(amadeus_runtime::consensus::consensus_muts::Mutation::Put { op: vec![], key, value })
212 }
213 "delete" => Ok(amadeus_runtime::consensus::consensus_muts::Mutation::Delete { op: vec![], key }),
214 "set_bit" => {
215 let value = m
216 .get(&Term::Atom(eetf::Atom::from("value")))
217 .and_then(|t| if let Term::FixInteger(i) = t { Some(i.value) } else { None })
218 .ok_or("no bit")? as u64;
219 let bloomsize = m
220 .get(&Term::Atom(eetf::Atom::from("bloomsize")))
221 .and_then(|t| if let Term::FixInteger(i) = t { Some(i.value) } else { None })
222 .ok_or("no size")? as u64;
223 Ok(amadeus_runtime::consensus::consensus_muts::Mutation::SetBit {
224 op: vec![],
225 key,
226 value,
227 bloomsize,
228 })
229 }
230 "clear_bit" => {
231 let value = m
232 .get(&Term::Atom(eetf::Atom::from("value")))
233 .and_then(|t| if let Term::FixInteger(i) = t { Some(i.value) } else { None })
234 .ok_or("no bit")? as u64;
235 Ok(amadeus_runtime::consensus::consensus_muts::Mutation::ClearBit { op: vec![], key, value })
236 }
237 _ => Err("unknown op".into()),
238 }
239 })
240 .collect()
241 }
242
243 fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
244 std::fs::create_dir_all(&dst)?;
245 for e in std::fs::read_dir(src)? {
246 let e = e?;
247 if e.file_type()?.is_dir() {
248 copy_dir(e.path(), dst.as_ref().join(e.file_name()))?;
249 } else {
250 std::fs::copy(e.path(), dst.as_ref().join(e.file_name()))?;
251 }
252 }
253 Ok(())
254 }
255
256 fn create_test_config() -> crate::config::Config {
257 let sk = crate::config::gen_sk();
258 let pk = crate::config::get_pk(&sk);
259 let pop = crate::utils::bls12_381::sign(&sk, pk.as_ref(), amadeus_utils::constants::DST_POP)
260 .map(|sig| sig.to_vec())
261 .unwrap_or_else(|_| vec![0u8; 96]);
262 crate::config::Config {
263 work_folder: "/tmp/test".to_string(),
264 version: crate::config::VERSION,
265 offline: false,
266 http_ipv4: std::net::Ipv4Addr::LOCALHOST,
267 http_port: 80,
268 udp_ipv4: std::net::Ipv4Addr::LOCALHOST,
269 udp_port: 36969,
270 public_ipv4: None,
271 seed_ips: vec![],
272 seed_anrs: vec![],
273 other_nodes: vec![],
274 trust_factor: 0.8,
275 max_peers: 300,
276 trainer_sk: sk,
277 trainer_pk: pk,
278 trainer_pk_b58: "test".to_string(),
279 trainer_pop: pop,
280 archival_node: false,
281 autoupdate: false,
282 computor_type: None,
283 snapshot_height: 0,
284 anr: None,
285 anr_name: None,
286 anr_desc: None,
287 }
288 }
289}