Skip to content

Commit 1fd155a

Browse files
authored
Merge branch 'feat/rbac-registration' into feat/registration-chain
2 parents 672a1d6 + a1cea39 commit 1fd155a

File tree

12 files changed

+257
-104
lines changed

12 files changed

+257
-104
lines changed

rust/cardano-chain-follower/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cardano-chain-follower"
3-
version = "0.0.4"
3+
version = "0.0.5"
44
edition.workspace = true
55
authors.workspace = true
66
homepage.workspace = true

rust/cardano-chain-follower/src/network.rs

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,38 @@ impl Network {
170170
///
171171
/// The Slot does not have to be a valid slot present in the blockchain.
172172
#[must_use]
173-
pub fn time_to_slot(&self, _time: DateTime<Utc>) -> Option<u64> {
174-
// TODO: Implement this, for now just return None.
175-
None
173+
pub fn time_to_slot(&self, time: DateTime<Utc>) -> Option<u64> {
174+
let genesis = self.genesis_values();
175+
176+
let byron_start_time = i64::try_from(genesis.byron_known_time)
177+
.map(|time| DateTime::<Utc>::from_timestamp(time, 0))
178+
.ok()??;
179+
let shelley_start_time = i64::try_from(genesis.shelley_known_time)
180+
.map(|time| DateTime::<Utc>::from_timestamp(time, 0))
181+
.ok()??;
182+
183+
// determine if the given time is in Byron or Shelley era.
184+
if time < byron_start_time {
185+
return None;
186+
}
187+
188+
if time < shelley_start_time {
189+
// Byron era
190+
let time_diff = time - byron_start_time;
191+
let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.byron_slot_length);
192+
193+
u64::try_from(elapsed_slots)
194+
.map(|elapsed_slots| Some(genesis.byron_known_slot + elapsed_slots))
195+
.ok()?
196+
} else {
197+
// Shelley era
198+
let time_diff = time - shelley_start_time;
199+
let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.shelley_slot_length);
200+
201+
u64::try_from(elapsed_slots)
202+
.map(|elapsed_slots| Some(genesis.shelley_known_slot + elapsed_slots))
203+
.ok()?
204+
}
176205
}
177206
}
178207

@@ -191,6 +220,7 @@ mod tests {
191220
use std::str::FromStr;
192221

193222
use anyhow::Ok;
223+
use chrono::{TimeZone, Utc};
194224

195225
use super::*;
196226

@@ -214,4 +244,153 @@ mod tests {
214244

215245
Ok(())
216246
}
247+
248+
#[test]
249+
fn test_time_to_slot_before_blockchain() {
250+
let network = Network::Mainnet;
251+
let genesis = network.genesis_values();
252+
253+
let before_blockchain = Utc
254+
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap() - 1, 0)
255+
.unwrap();
256+
257+
assert_eq!(network.time_to_slot(before_blockchain), None);
258+
}
259+
260+
#[test]
261+
fn test_time_to_slot_byron_era() {
262+
let network = Network::Mainnet;
263+
let genesis = network.genesis_values();
264+
265+
let byron_start_time = Utc
266+
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap(), 0)
267+
.unwrap();
268+
let byron_slot_length = i64::from(genesis.byron_slot_length);
269+
270+
// a time in the middle of the Byron era.
271+
let time = byron_start_time + chrono::Duration::seconds(byron_slot_length * 100);
272+
let expected_slot = genesis.byron_known_slot + 100;
273+
274+
assert_eq!(network.time_to_slot(time), Some(expected_slot));
275+
}
276+
277+
#[test]
278+
fn test_time_to_slot_transition_to_shelley() {
279+
let network = Network::Mainnet;
280+
let genesis = network.genesis_values();
281+
282+
let shelley_start_time = Utc
283+
.timestamp_opt(i64::try_from(genesis.shelley_known_time).unwrap(), 0)
284+
.unwrap();
285+
let byron_slot_length = i64::from(genesis.byron_slot_length);
286+
287+
// a time just before Shelley era starts.
288+
let time = shelley_start_time - chrono::Duration::seconds(1);
289+
let elapsed_slots = (time
290+
- Utc
291+
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap(), 0)
292+
.unwrap())
293+
.num_seconds()
294+
/ byron_slot_length;
295+
let expected_slot = genesis.byron_known_slot + u64::try_from(elapsed_slots).unwrap();
296+
297+
assert_eq!(network.time_to_slot(time), Some(expected_slot));
298+
}
299+
300+
#[test]
301+
fn test_time_to_slot_shelley_era() {
302+
let network = Network::Mainnet;
303+
let genesis = network.genesis_values();
304+
305+
let shelley_start_time = Utc
306+
.timestamp_opt(i64::try_from(genesis.shelley_known_time).unwrap(), 0)
307+
.unwrap();
308+
let shelley_slot_length = i64::from(genesis.shelley_slot_length);
309+
310+
// a time in the middle of the Shelley era.
311+
let time = shelley_start_time + chrono::Duration::seconds(shelley_slot_length * 200);
312+
let expected_slot = genesis.shelley_known_slot + 200;
313+
314+
assert_eq!(network.time_to_slot(time), Some(expected_slot));
315+
}
316+
317+
#[test]
318+
fn test_slot_to_time_to_slot_consistency() {
319+
let network = Network::Mainnet;
320+
321+
// a few arbitrary slots in different ranges.
322+
let slots_to_test = vec![0, 10_000, 1_000_000, 50_000_000];
323+
324+
for slot in slots_to_test {
325+
let time = network.slot_to_time(slot);
326+
let calculated_slot = network.time_to_slot(time);
327+
328+
assert_eq!(calculated_slot, Some(slot), "Failed for slot: {slot}");
329+
}
330+
}
331+
332+
#[test]
333+
#[allow(clippy::panic)]
334+
fn test_time_to_slot_to_time_consistency() {
335+
let network = Network::Mainnet;
336+
let genesis = network.genesis_values();
337+
338+
// Byron, Shelley, and Conway.
339+
let times_to_test = vec![
340+
Utc.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap() + 100, 0)
341+
.unwrap(),
342+
Utc.timestamp_opt(
343+
i64::try_from(genesis.shelley_known_time).unwrap() + 1_000,
344+
0,
345+
)
346+
.unwrap(),
347+
Utc.timestamp_opt(
348+
i64::try_from(genesis.shelley_known_time).unwrap() + 10_000_000,
349+
0,
350+
)
351+
.unwrap(),
352+
];
353+
354+
for time in times_to_test {
355+
if let Some(slot) = network.time_to_slot(time) {
356+
let calculated_time = network.slot_to_time(slot);
357+
358+
assert_eq!(
359+
calculated_time.timestamp(),
360+
time.timestamp(),
361+
"Failed for time: {time}"
362+
);
363+
} else {
364+
panic!("time_to_slot returned None for a valid time: {time}");
365+
}
366+
}
367+
}
368+
369+
#[test]
370+
fn test_conway_era_time_to_slot_and_back() {
371+
let network = Network::Mainnet;
372+
let genesis = network.genesis_values();
373+
374+
// a very late time, far in the Conway era.
375+
let conway_time = Utc
376+
.timestamp_opt(
377+
i64::try_from(genesis.shelley_known_time).unwrap() + 20_000_000,
378+
0,
379+
)
380+
.unwrap();
381+
382+
let slot = network.time_to_slot(conway_time);
383+
assert!(
384+
slot.is_some(),
385+
"Failed to calculate slot for Conway era time"
386+
);
387+
388+
let calculated_time = network.slot_to_time(slot.unwrap());
389+
390+
assert_eq!(
391+
calculated_time.timestamp(),
392+
conway_time.timestamp(),
393+
"Inconsistency for Conway era time"
394+
);
395+
}
217396
}

rust/rbac-registration/src/cardano/cip509/mod.rs

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,7 @@ impl Cip509 {
205205
for role in role_set {
206206
if role.role_number == 0 {
207207
stake_key_validate =
208-
validate_stake_public_key(self, txn, txn_idx, validation_report)
209-
.unwrap_or(false);
208+
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
210209
payment_key_validate =
211210
validate_payment_key(txn, txn_idx, role, validation_report)
212211
.unwrap_or(false);
@@ -221,38 +220,3 @@ impl Cip509 {
221220
&& signing_key
222221
}
223222
}
224-
225-
#[cfg(test)]
226-
mod tests {
227-
228-
use super::*;
229-
230-
#[test]
231-
fn test_decode_cip509() {
232-
// This data is from conway_1.block
233-
let cip_509 = "a50050ca7a1457ef9f4c7f9c747f8c4a4cfa6c0150226d126819472b7afad7d0b8c7b89aa20258204d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c20b9458401b03060066006fd5b67002167882eac0b5f2b11da40788a39bfa0324c494f7003a6b4c1c4bac378e322cb280230a4002f5b2754e863806f7e524afc99996aa28584032f02b600cbf04c6a09e05100880a09ee59b6627dc78d68175469b8c5b1fac141a6da5c6c2ea446597b6f0b6efea00a04ac0c1756455589908a5e089ba604a1258405917d6ee2b2535959d806c00eb2958929ababb40d681b5245751538e915d3d90f561ddcaa9aaa9cd78a30882a22a99c742c4f7610b43750a0d6651e8640a8d4c58402167427cfa933d6430c026640888210cd0c4e93e7015100300dcaef47b9c155ea4ccb27773c27f5d6a44fbf98065a14e5f0eca530e57082a971cbf22fa9065585840ae72e2a061eb558d3fd7727e87a8f07b5faf0d3cedf8d99ab6e0c845f5dd3ce78d31d7365c523b5a4dfe5d35bfafaefb2f60dd7473cbe8d6aa6bf557b1fbdf775840bf96bcd3ffdbfc7d20b65be7f5c7dba1cf635e3b449bb644bdfc73e0e49a5db73edddc7ed331220ba732f62f3aee8503f5c6f9bd5f7fedb37dc6580196052e50584027fdd7e8bfe9146561ad1ebc79ecef0ee1df7081cf9cd1fd929569ef3d55972d5b7ff882ce2213f789fc08787164f14aa86d55e98e332b220a07fa464aaa7c335840ce4bcfb268ed577f72e87fdea4442107bf2da93fe05121d5befa7ae5aecc5f3f9c732e82108003166380198c0146b0e214114a31d7c62b0ec18afd5834034c2b58402b2c515b350d8980a16932071b6d8d125ea1eb53dc28a8aee1787a1670b9e8c4c8cb00c726f3515a39ca1689f870295752820a64721e05e1a234710583416316584031d80291ac9a2b66a04cba844b85f9928a4a04a9928b2805124a25b3aaa4422e45e5d422a9b88a028ba4a5123ac244b8b472164b86085ac21357c3aae7696be25840f1104878009b03813d9e6c53255722402090206058a009d2b808aff772fb712d75f1dea09507fd83838e045dd9ce3eb59e4554f5ed02b8aeb60700f4b39dd9fe584064e1d5a137de0fa4c6cccfd71f831bee372756d72990b357a44e2f9eaf3854db65379db466cfcb55517ba71550acade564f4b7efd1fd95fa57228cee6fa9ae3458405ce1ae79b77f7cd5bdecfcb800fbdb7eaf720eae5995176d94a07c326c71aaf5e6f8439e577edb2d1ed64959324b5a7476e9159bf37bdf226edb747787b79b9e5840bc6ab5b84714eefa4a8c2df4aba37a36757d8b39dd79ec41b4a2f3ee96eabdc0e1f65b37264bdbfdf79eebbc820a7deab4e39f7e1cbf6610402fd8fb55fbef3d584038226e4d37c42970c830184b2e1c5026eadb9677ae8f6d300975ca6ceec5c8920382e827c1f636f7dd9f8d492737f4520a944bfeebba5ca2d5efa80ad453a43f584004c357ecccfc4dab75ce560b0300db9092ced52625d0c8df6fc89da9a45b6dc9c2461f21e6ee7b7afd877fbd8c1a1fa7ff38fa506e14749ebb68e24571c6220c584004208c284d628c2148b252f91b8b50014b080b040554095b52ca862bb974218222d412112ae5d2584c54584ae157f22b183cb4ba9c5fc42ba6894ad074ffe0875840c69ee921211d0ce4cd0f89b7e708163b3ab9286fe26a8c68ed85930cabc5dbfed7f9681c535dbdbfeb56f7a2b32d1f43de1dbcc934676edefacb3df7c1210067584064a1b8d94448b7f22a77dc736edb12f7c2c52b2eb8d4a80b78147d89f9a3a0659c03e10bbb336e391b3961f1afbfa08af3de2a817fceddea0cb57f438b0f8947581e9782ee92e890df65636d835d2d465cc5521c0ec05470e002800015eecf5818635840e0427f23196c17cf13f030595335343030c11d914bc7a84b56af7040930af4110fd4ca29b0bc0e83789adb8668ea2ef28c1dd10dc1fd35ea6ae8c06ee769540d";
234-
let binding = hex::decode(cip_509).unwrap();
235-
let mut decoder = Decoder::new(binding.as_slice());
236-
let decoded_cip509 = Cip509::decode(&mut decoder, &mut ()).unwrap();
237-
238-
let purpose: [u8; 16] = hex::decode("ca7a1457ef9f4c7f9c747f8c4a4cfa6c")
239-
.unwrap()
240-
.try_into()
241-
.unwrap();
242-
let txn_inputs_hash: [u8; 16] = hex::decode("226d126819472b7afad7d0b8c7b89aa2")
243-
.unwrap()
244-
.try_into()
245-
.unwrap();
246-
let prv_tx_id: [u8; 32] =
247-
hex::decode("4d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c2")
248-
.unwrap()
249-
.try_into()
250-
.unwrap();
251-
let validation_signature = hex::decode("e0427f23196c17cf13f030595335343030c11d914bc7a84b56af7040930af4110fd4ca29b0bc0e83789adb8668ea2ef28c1dd10dc1fd35ea6ae8c06ee769540d").unwrap();
252-
253-
assert_eq!(decoded_cip509.purpose, UuidV4(purpose));
254-
assert_eq!(decoded_cip509.txn_inputs_hash, TxInputHash(txn_inputs_hash));
255-
assert_eq!(decoded_cip509.prv_tx_id, Some(prv_tx_id.into()));
256-
assert_eq!(decoded_cip509.validation_signature, validation_signature);
257-
}
258-
}

rust/rbac-registration/src/cardano/cip509/rbac/pub_key.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ impl Decode<'_, ()> for SimplePublicKeyType {
6767
ed25519.copy_from_slice(&bytes);
6868
Ok(Self::Ed25519(Ed25519PublicKey(ed25519)))
6969
} else {
70-
Err(decode::Error::message("Invalid length for Ed25519 key"))
70+
Err(decode::Error::message(format!(
71+
"Invalid length for Ed25519 key, got {}",
72+
bytes.len()
73+
)))
7174
}
7275
},
7376
_ => Err(decode::Error::message("Unknown tag for Self")),

rust/rbac-registration/src/cardano/cip509/rbac/role_data.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ pub struct RoleData {
1414
/// Role number.
1515
pub role_number: u8,
1616
/// Optional role signing key.
17-
pub role_signing_key: Option<Vec<KeyLocalRef>>,
17+
pub role_signing_key: Option<KeyLocalRef>,
1818
/// Optional role encryption key.
19-
pub role_encryption_key: Option<Vec<KeyLocalRef>>,
19+
pub role_encryption_key: Option<KeyLocalRef>,
2020
/// Optional payment key.
2121
pub payment_key: Option<i16>,
2222
/// Optional role extended data keys.
@@ -56,20 +56,12 @@ impl Decode<'_, ()> for RoleData {
5656
role_data.role_number = decode_helper(d, "RoleNumber in RoleData", ctx)?;
5757
},
5858
RoleDataInt::RoleSigningKey => {
59-
let arr_len = decode_array_len(d, "RoleSigningKey")?;
60-
let mut role_signing_key = Vec::new();
61-
for _ in 0..arr_len {
62-
role_signing_key.push(KeyLocalRef::decode(d, ctx)?);
63-
}
64-
role_data.role_signing_key = Some(role_signing_key);
59+
decode_array_len(d, "RoleSigningKey")?;
60+
role_data.role_signing_key = Some(KeyLocalRef::decode(d, ctx)?);
6561
},
6662
RoleDataInt::RoleEncryptionKey => {
67-
let arr_len = decode_array_len(d, "RoleEncryptionKey")?;
68-
let mut role_encryption_key = Vec::new();
69-
for _ in 0..arr_len {
70-
role_encryption_key.push(KeyLocalRef::decode(d, ctx)?);
71-
}
72-
role_data.role_encryption_key = Some(role_encryption_key);
63+
decode_array_len(d, "RoleEncryptionKey")?;
64+
role_data.role_encryption_key = Some(KeyLocalRef::decode(d, ctx)?);
7365
},
7466
RoleDataInt::PaymentKey => {
7567
role_data.payment_key =

0 commit comments

Comments
 (0)