Skip to content

Commit 7ddd2f0

Browse files
committed
feat: update ocert counters when block is validated successfully
1 parent 007a358 commit 7ddd2f0

File tree

6 files changed

+132
-78
lines changed

6 files changed

+132
-78
lines changed

modules/block_kes_validator/src/block_kes_validator.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ impl BlockKesValidator {
118118
let result = state
119119
.validate_block_kes(block_info, &block_msg.header, &genesis)
120120
.map_err(|e| *e);
121+
122+
// Update the operational certificate counter
123+
// When block is validated successfully
124+
// Reference
125+
// https://github.com/IntersectMBO/ouroboros-consensus/blob/e3c52b7c583bdb6708fac4fdaa8bf0b9588f5a88/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs#L508
126+
if let Ok(Some((pool_id, updated_sequence_number))) = result.as_ref() {
127+
state.update_ocert_counter(*pool_id, *updated_sequence_number);
128+
}
129+
121130
if let Err(e) = kes_validation_publisher
122131
.publish_kes_validation(block_info, result)
123132
.await

modules/block_kes_validator/src/kes_validation_publisher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use acropolis_common::{
22
messages::{CardanoMessage, Message},
33
validation::{KesValidationError, ValidationError, ValidationStatus},
4-
BlockInfo,
4+
BlockInfo, PoolId,
55
};
66
use caryatid_sdk::Context;
77
use std::sync::Arc;
@@ -25,7 +25,7 @@ impl KesValidationPublisher {
2525
pub async fn publish_kes_validation(
2626
&mut self,
2727
block: &BlockInfo,
28-
validation_result: Result<(), KesValidationError>,
28+
validation_result: Result<Option<(PoolId, u64)>, KesValidationError>,
2929
) -> anyhow::Result<()> {
3030
let validation_status = match validation_result {
3131
Ok(_) => ValidationStatus::Go,

modules/block_kes_validator/src/ouroboros/kes_validation.rs

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashSet;
2+
13
use acropolis_common::{
24
crypto::keyhash_224,
35
validation::{
@@ -12,9 +14,13 @@ use crate::ouroboros::{kes, praos, tpraos};
1214

1315
#[derive(Copy, Clone)]
1416
pub struct OperationalCertificate<'a> {
17+
/// The operational hot key
1518
pub operational_cert_hot_vkey: &'a [u8],
19+
/// The sequence number of the operational certificate
1620
pub operational_cert_sequence_number: u64,
21+
/// The KES period of the operational certificate
1722
pub operational_cert_kes_period: u64,
23+
/// The signature of the operational certificate
1824
pub operational_cert_sigma: &'a [u8],
1925
}
2026

@@ -88,6 +94,8 @@ pub fn validate_operational_certificate<'a>(
8894
}
8995

9096
// The opcert message is a concatenation of the KES vkey, the sequence number, and the kes period
97+
// Reference
98+
// https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/libs/cardano-protocol-tpraos/src/Cardano/Protocol/TPraos/OCert.hs#L144
9199
let mut message = Vec::new();
92100
message.extend_from_slice(certificate.operational_cert_hot_vkey);
93101
message.extend_from_slice(&certificate.operational_cert_sequence_number.to_be_bytes());
@@ -101,65 +109,79 @@ pub fn validate_operational_certificate<'a>(
101109
Ok(())
102110
}
103111

112+
/// This function check block header's KES signature and operational certificate
113+
/// return validation functions for KES signature and operational certificate
114+
/// and the pool id and declared sequence number (which will be used to update the operational certificate counter when validation is successful)
115+
/// Reference
116+
/// https://github.com/IntersectMBO/ouroboros-consensus/blob/e3c52b7c583bdb6708fac4fdaa8bf0b9588f5a88/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs#L612
104117
pub fn validate_block_kes<'a>(
105118
header: &'a MultiEraHeader,
106119
ocert_counters: &'a HashMap<PoolId, u64>,
107-
active_spos: &'a [PoolId],
120+
active_spos: &'a HashSet<PoolId>,
108121
genesis_delegs: &'a GenesisDelegates,
109122
slots_per_kes_period: u64,
110123
max_kes_evolutions: u64,
111-
) -> Result<Vec<KesValidation<'a>>, Box<KesValidationError>> {
124+
) -> Result<(Vec<KesValidation<'a>>, PoolId, u64), Box<KesValidationError>> {
112125
let is_praos = matches!(header, MultiEraHeader::BabbageCompatible(_));
113126

114127
let issuer_vkey = header.issuer_vkey().ok_or(Box::new(KesValidationError::Other(
115-
"Issuer Key is not set".to_string(),
128+
"Block header missing issuer verification key".to_string(),
116129
)))?;
117130
let issuer = ed25519::PublicKey::from(
118-
<[u8; ed25519::PublicKey::SIZE]>::try_from(issuer_vkey)
119-
.map_err(|_| Box::new(KesValidationError::Other("Invalid issuer key".to_string())))?,
131+
<[u8; ed25519::PublicKey::SIZE]>::try_from(issuer_vkey).map_err(|_| {
132+
Box::new(KesValidationError::Other(
133+
"Issuer verification key has invalid length (expected 32 bytes)".to_string(),
134+
))
135+
})?,
120136
);
121137
let pool_id = PoolId::from(keyhash_224(issuer_vkey));
122138

123139
let slot_kes_period = header.slot() / slots_per_kes_period;
124140
let cert = operational_cert(header).ok_or(Box::new(KesValidationError::Other(
125-
"Operational certificate is not set".to_string(),
141+
"Block header missing operational certificate".to_string(),
126142
)))?;
127143
let body_sig = body_signature(header).ok_or(Box::new(KesValidationError::Other(
128-
"Body signature is not set".to_string(),
144+
"Block header missing KES body signature".to_string(),
129145
)))?;
130146
let raw_header_body = header.header_body_cbor().ok_or(Box::new(KesValidationError::Other(
131-
"Header body is not set".to_string(),
147+
"Block header body CBOR not available".to_string(),
132148
)))?;
133149

150+
let declared_sequence_number = cert.operational_cert_sequence_number;
134151
let latest_sequence_number = if is_praos {
135152
praos::latest_issue_no_praos(ocert_counters, active_spos, &pool_id)
136153
} else {
137154
tpraos::latest_issue_no_tpraos(ocert_counters, active_spos, genesis_delegs, &pool_id)
138155
}
139156
.ok_or(Box::new(KesValidationError::NoOCertCounter { pool_id }))?;
140157

141-
Ok(vec![
142-
Box::new(move || {
143-
validate_kes_signature(
144-
slot_kes_period,
145-
cert.operational_cert_kes_period,
146-
raw_header_body,
147-
&kes::PublicKey::try_from(cert.operational_cert_hot_vkey).map_err(|_| {
148-
KesValidationError::Other(
149-
"Invalid operational certificate hot vkey".to_string(),
150-
)
151-
})?,
152-
&kes::Signature::try_from(body_sig)
153-
.map_err(|_| KesValidationError::Other("Invalid body signature".to_string()))?,
154-
max_kes_evolutions,
155-
)?;
156-
Ok(())
157-
}),
158-
Box::new(move || {
159-
validate_operational_certificate(cert, &issuer, latest_sequence_number, is_praos)?;
160-
Ok(())
161-
}),
162-
])
158+
Ok((
159+
vec![
160+
Box::new(move || {
161+
validate_kes_signature(
162+
slot_kes_period,
163+
cert.operational_cert_kes_period,
164+
raw_header_body,
165+
&kes::PublicKey::try_from(cert.operational_cert_hot_vkey).map_err(|_| {
166+
KesValidationError::Other(
167+
"Invalid operational certificate hot vkey".to_string(),
168+
)
169+
})?,
170+
&kes::Signature::try_from(body_sig).map_err(|_| {
171+
KesValidationError::Other("Invalid body signature".to_string())
172+
})?,
173+
max_kes_evolutions,
174+
)?;
175+
Ok(())
176+
}),
177+
Box::new(move || {
178+
validate_operational_certificate(cert, &issuer, latest_sequence_number, is_praos)?;
179+
Ok(())
180+
}),
181+
],
182+
pool_id,
183+
declared_sequence_number,
184+
))
163185
}
164186

165187
fn operational_cert<'a>(header: &'a MultiEraHeader) -> Option<OperationalCertificate<'a>> {
@@ -213,7 +235,7 @@ mod tests {
213235
MultiEraHeader::decode(Era::Shelley as u8, None, &block_header_4490511).unwrap();
214236

215237
let ocert_counters = HashMap::new();
216-
let active_spos = vec![];
238+
let active_spos = HashSet::new();
217239

218240
let result = validate_block_kes(
219241
&block_header,
@@ -223,10 +245,12 @@ mod tests {
223245
slots_per_kes_period,
224246
max_kes_evolutions,
225247
)
226-
.and_then(|kes_validations| {
227-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
248+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
249+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
250+
Ok((pool_id, declared_sequence_number))
228251
});
229252
assert!(result.is_ok());
253+
assert_eq!(result.unwrap().1, 0);
230254
}
231255

232256
#[test]
@@ -245,11 +269,10 @@ mod tests {
245269
.unwrap(),
246270
1,
247271
)]);
248-
let active_spos =
249-
vec![
250-
PoolId::from_bech32("pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy")
251-
.unwrap(),
252-
];
272+
let active_spos = HashSet::from_iter([PoolId::from_bech32(
273+
"pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",
274+
)
275+
.unwrap()]);
253276

254277
let result = validate_block_kes(
255278
&block_header,
@@ -259,10 +282,12 @@ mod tests {
259282
slots_per_kes_period,
260283
max_kes_evolutions,
261284
)
262-
.and_then(|kes_validations| {
263-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
285+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
286+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
287+
Ok((pool_id, declared_sequence_number))
264288
});
265289
assert!(result.is_ok());
290+
assert_eq!(result.unwrap().1, 1);
266291
}
267292

268293
#[test]
@@ -281,11 +306,10 @@ mod tests {
281306
.unwrap(),
282307
2,
283308
)]);
284-
let active_spos =
285-
vec![
286-
PoolId::from_bech32("pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy")
287-
.unwrap(),
288-
];
309+
let active_spos = HashSet::from_iter([PoolId::from_bech32(
310+
"pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",
311+
)
312+
.unwrap()]);
289313

290314
let result = validate_block_kes(
291315
&block_header,
@@ -295,8 +319,9 @@ mod tests {
295319
slots_per_kes_period,
296320
max_kes_evolutions,
297321
)
298-
.and_then(|kes_validations| {
299-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
322+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
323+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
324+
Ok((pool_id, declared_sequence_number))
300325
});
301326
assert!(result.is_err());
302327
assert_eq!(
@@ -322,7 +347,7 @@ mod tests {
322347
MultiEraHeader::decode(Era::Shelley as u8, None, &block_header_4556956).unwrap();
323348

324349
let ocert_counters = HashMap::new();
325-
let active_spos = vec![];
350+
let active_spos = HashSet::new();
326351

327352
let result = validate_block_kes(
328353
&block_header,
@@ -332,9 +357,11 @@ mod tests {
332357
slots_per_kes_period,
333358
max_kes_evolutions,
334359
)
335-
.and_then(|kes_validations| {
336-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
360+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
361+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
362+
Ok((pool_id, declared_sequence_number))
337363
});
364+
338365
assert!(result.is_err());
339366
assert_eq!(
340367
result.unwrap_err(),
@@ -363,11 +390,10 @@ mod tests {
363390
.unwrap(),
364391
11,
365392
)]);
366-
let active_spos =
367-
vec![
368-
PoolId::from_bech32("pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t")
369-
.unwrap(),
370-
];
393+
let active_spos = HashSet::from_iter([PoolId::from_bech32(
394+
"pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t",
395+
)
396+
.unwrap()]);
371397

372398
let result = validate_block_kes(
373399
&block_header,
@@ -377,8 +403,9 @@ mod tests {
377403
slots_per_kes_period,
378404
max_kes_evolutions,
379405
)
380-
.and_then(|kes_validations| {
381-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
406+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
407+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
408+
Ok((pool_id, declared_sequence_number))
382409
});
383410
assert!(result.is_ok());
384411
}
@@ -402,11 +429,10 @@ mod tests {
402429
// now ocert counter is incremented by 2
403430
9,
404431
)]);
405-
let active_spos =
406-
vec![
407-
PoolId::from_bech32("pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t")
408-
.unwrap(),
409-
];
432+
let active_spos = HashSet::from_iter([PoolId::from_bech32(
433+
"pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t",
434+
)
435+
.unwrap()]);
410436

411437
let result = validate_block_kes(
412438
&block_header,
@@ -416,8 +442,9 @@ mod tests {
416442
slots_per_kes_period,
417443
max_kes_evolutions,
418444
)
419-
.and_then(|kes_validations| {
420-
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))
445+
.and_then(|(kes_validations, pool_id, declared_sequence_number)| {
446+
kes_validations.iter().try_for_each(|assert| assert().map_err(Box::new))?;
447+
Ok((pool_id, declared_sequence_number))
421448
});
422449
assert!(result.is_err());
423450
assert_eq!(

modules/block_kes_validator/src/ouroboros/praos.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
use std::collections::HashSet;
2+
13
use acropolis_common::PoolId;
24
use imbl::HashMap;
35

46
pub fn latest_issue_no_praos(
5-
ocert_counter: &HashMap<PoolId, u64>,
6-
active_spos: &[PoolId],
7+
ocert_counters: &HashMap<PoolId, u64>,
8+
active_spos: &HashSet<PoolId>,
79
pool_id: &PoolId,
810
) -> Option<u64> {
9-
ocert_counter.get(pool_id).copied().or(if active_spos.contains(pool_id) {
11+
ocert_counters.get(pool_id).copied().or(if active_spos.contains(pool_id) {
1012
Some(0)
1113
} else {
1214
None

modules/block_kes_validator/src/ouroboros/tpraos.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
use std::collections::HashSet;
2+
13
use acropolis_common::{GenesisDelegates, PoolId};
24
use imbl::HashMap;
35

6+
/// This function is used to get the latest issue number for a given pool id.
7+
/// First check ocert_counters
8+
/// Check if the pool is in active_spos (registered or not)
9+
/// And if the pool is a genesis delegate
10+
/// Reference
11+
/// https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/libs/cardano-protocol-tpraos/src/Cardano/Protocol/TPraos/OCert.hs#L66
412
pub fn latest_issue_no_tpraos(
5-
ocert_counter: &HashMap<PoolId, u64>,
6-
active_spos: &[PoolId],
13+
ocert_counters: &HashMap<PoolId, u64>,
14+
active_spos: &HashSet<PoolId>,
715
genesis_delegs: &GenesisDelegates,
816
pool_id: &PoolId,
917
) -> Option<u64> {
10-
ocert_counter.get(pool_id).copied().or(if active_spos.contains(pool_id) {
18+
ocert_counters.get(pool_id).copied().or(if active_spos.contains(pool_id) {
1119
Some(0)
1220
} else {
1321
genesis_delegs.as_ref().values().any(|v| v.delegate.eq(pool_id.as_ref())).then_some(0)

0 commit comments

Comments
 (0)