Skip to content

Commit a7a5251

Browse files
Cip509 constructors
1 parent 154d1da commit a7a5251

File tree

3 files changed

+184
-49
lines changed

3 files changed

+184
-49
lines changed

rust/cardano-blockchain-types/src/txn_index.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ pub struct TxnIndex(u16);
88

99
impl TxnIndex {
1010
/// Convert an `<T>` to transaction index (saturate if out of range).
11-
pub(crate) fn from_saturating<
11+
pub fn from_saturating<
1212
T: Copy
1313
+ TryInto<u16>
1414
+ std::ops::Sub<Output = T>
15-
+ std::cmp::PartialOrd<T>
15+
+ PartialOrd<T>
1616
+ num_traits::identities::Zero,
1717
>(
1818
value: T,
@@ -21,3 +21,9 @@ impl TxnIndex {
2121
Self(value)
2222
}
2323
}
24+
25+
impl From<TxnIndex> for usize {
26+
fn from(value: TxnIndex) -> Self {
27+
value.0.into()
28+
}
29+
}

rust/catalyst-types/src/problem_report.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use orx_concurrent_vec::ConcurrentVec;
1010
use serde::{ser::SerializeSeq, Serialize};
1111

1212
/// The kind of problem being reported
13-
#[derive(Serialize, Clone)]
13+
#[derive(Debug, Serialize, Clone)]
1414
#[serde(tag = "type")]
1515
enum Kind {
1616
/// Expected and Required field is missing
@@ -64,7 +64,7 @@ enum Kind {
6464
}
6565

6666
/// Problem Report Entry
67-
#[derive(Serialize, Clone)]
67+
#[derive(Debug, Serialize, Clone)]
6868
struct Entry {
6969
/// The kind of problem we are recording.
7070
kind: Kind,
@@ -73,7 +73,7 @@ struct Entry {
7373
}
7474

7575
/// The Problem Report list
76-
#[derive(Clone)]
76+
#[derive(Debug, Clone)]
7777
struct Report(ConcurrentVec<Entry>);
7878

7979
impl Serialize for Report {
@@ -88,7 +88,7 @@ impl Serialize for Report {
8888
}
8989

9090
/// The Problem Report list
91-
#[derive(Clone)]
91+
#[derive(Debug, Clone)]
9292
struct Context(Arc<String>);
9393

9494
impl Serialize for Context {
@@ -100,7 +100,7 @@ impl Serialize for Context {
100100
}
101101

102102
/// Problem Report
103-
#[derive(Clone, Serialize)]
103+
#[derive(Debug, Clone, Serialize)]
104104
pub struct ProblemReport {
105105
/// What context does the whole report have
106106
context: Context,

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

Lines changed: 171 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@
77
pub mod rbac;
88
pub mod types;
99
pub mod utils;
10-
pub(crate) mod validation;
1110
pub mod x509_chunks;
1211

13-
use cardano_blockchain_types::hashes::{Blake2b256Hash, BLAKE_2B256_SIZE};
12+
pub(crate) mod validation;
13+
14+
use anyhow::anyhow;
15+
use cardano_blockchain_types::{
16+
hashes::{Blake2b256Hash, BLAKE_2B256_SIZE},
17+
MultiEraBlock, Slot, TxnIndex,
18+
};
1419
use catalyst_types::problem_report::ProblemReport;
1520
use minicbor::{
1621
decode::{self},
1722
Decode, Decoder,
1823
};
19-
use pallas::ledger::traverse::MultiEraTx;
24+
use pallas::{codec::utils::Nullable, ledger::traverse::MultiEraTx};
2025
use strum_macros::FromRepr;
26+
use tracing::warn;
2127
use uuid::Uuid;
2228
use validation::{
2329
validate_aux, validate_payment_key, validate_role_singing_key, validate_stake_public_key,
@@ -26,10 +32,13 @@ use validation::{
2632

2733
use super::transaction::witness::TxWitness;
2834
use crate::{
29-
cardano::cip509::{
30-
rbac::{Cip509RbacMetadata, RoleData, RoleNumber},
31-
types::{TxInputHash, ValidationSignature},
32-
x509_chunks::X509Chunks,
35+
cardano::{
36+
cip509::{
37+
rbac::{Cip509RbacMetadata, RoleData, RoleNumber},
38+
types::{TxInputHash, ValidationSignature},
39+
x509_chunks::X509Chunks,
40+
},
41+
transaction::raw_aux_data::RawAuxData,
3342
},
3443
utils::{
3544
decode_helper::{
@@ -49,7 +58,7 @@ pub const LABEL: u64 = 509;
4958
/// more details.
5059
///
5160
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md
52-
#[derive(Debug, PartialEq, Clone, Default)]
61+
#[derive(Debug, Clone)]
5362
pub struct Cip509 {
5463
/// A registration purpose (`UUIDv4`).
5564
///
@@ -65,8 +74,18 @@ pub struct Cip509 {
6574
///
6675
/// This field encoded in chunks. See [`X509Chunks`] for more details.
6776
metadata: Option<Cip509RbacMetadata>,
68-
/// Validation signature.
77+
/// A validation signature.
6978
validation_signature: Option<ValidationSignature>,
79+
/// A report potentially containing all the issues occurred during `Cip509` decoding
80+
/// and validation.
81+
///
82+
/// The data located in `Cip509` is only considered valid if
83+
/// `ProblemReport::is_problematic()` returns false.
84+
report: ProblemReport,
85+
/// A slot identifying the block that this `Cip509` was extracted from.
86+
slot: Slot,
87+
/// A transaction index.
88+
transaction_index: TxnIndex,
7089
}
7190

7291
/// Validation value for CIP509 metadatum.
@@ -110,8 +129,105 @@ enum Cip509IntIdentifier {
110129
}
111130

112131
impl Cip509 {
113-
// TODO: FIXME: Replace validation with construction from `MultiEraBlock` and `TxnIndex`.
114-
// (https://github.com/input-output-hk/catalyst-libs/pull/127#discussion_r1901549418)
132+
/// Returns a `Cip509` instance if it is present in the given transaction, otherwise
133+
/// `None` is returned.
134+
///
135+
/// # Errors
136+
///
137+
/// An error is only returned if the data is completely corrupted. In all other cases
138+
/// the `Cip509` structure contains fully or partially decoded data.
139+
pub fn new(block: &MultiEraBlock, index: TxnIndex) -> Result<Option<Self>, anyhow::Error> {
140+
let block = block.decode();
141+
let transactions = block.txs();
142+
let transaction = transactions.get(usize::from(index)).ok_or_else(|| {
143+
anyhow!(
144+
"Invalid transaction index {index:?}, transactions count = {}",
145+
block.tx_count()
146+
)
147+
})?;
148+
149+
let MultiEraTx::Conway(transaction) = transaction else {
150+
return Err(anyhow!("Unsupported era: {}", transaction.era()));
151+
};
152+
153+
let auxiliary_data = match &transaction.auxiliary_data {
154+
Nullable::Some(v) => v.raw_cbor(),
155+
_ => return Ok(None),
156+
};
157+
let auxiliary_data = RawAuxData::new(auxiliary_data);
158+
let Some(metadata) = auxiliary_data.get_metadata(509) else {
159+
return Ok(None);
160+
};
161+
162+
let mut decoder = Decoder::new(metadata.as_slice());
163+
let mut report = ProblemReport::new("Decoding and validating Cip509");
164+
let mut cip509 = match Cip509::decode(&mut decoder, &mut report) {
165+
Ok(v) => v,
166+
Err(e) => {
167+
report.other(&format!("{e:?}"), "Failed to decode Cip509");
168+
return Ok(Some(Self::with_slot_and_index(
169+
report,
170+
block.slot().into(),
171+
index,
172+
)));
173+
},
174+
};
175+
cip509.slot = block.slot().into();
176+
cip509.transaction_index = index;
177+
178+
// After this point the decoding is finished and the structure shouldn't be modified
179+
// except of populating the problem report during validation.
180+
let cip509 = cip509;
181+
182+
// TODO: FIXME: Validation!
183+
todo!();
184+
185+
Ok(Some(cip509))
186+
}
187+
188+
/// Returns a list of Cip509 instances from all the transactions of the given block.
189+
pub fn from_block(block: &MultiEraBlock) -> Vec<Self> {
190+
let mut result = Vec::new();
191+
192+
let decoded_block = block.decode();
193+
for index in 0..decoded_block.tx_count() {
194+
let index = TxnIndex::from_saturating(index);
195+
match Self::new(block, index) {
196+
Ok(Some(v)) => result.push(v),
197+
// Normal situation: there is no Cip509 data in this transaction.
198+
Ok(None) => {},
199+
Err(e) => {
200+
warn!(
201+
"Unable to extract Cip509 from the {} block {index:?} transaction: {e:?}",
202+
decoded_block.slot()
203+
);
204+
},
205+
}
206+
}
207+
208+
result
209+
}
210+
211+
/// Creates an "empty" `Cip509` instance with all fields set to `None`. Non-optional
212+
/// fields set to dummy values that must be overwritten.
213+
fn with_report(report: ProblemReport) -> Self {
214+
Self::with_slot_and_index(report, 0.into(), TxnIndex::from_saturating(0))
215+
}
216+
217+
/// Creates an "empty" `Cip509` instance with all fields set to `None`. Should only be
218+
/// used internally.
219+
fn with_slot_and_index(report: ProblemReport, slot: Slot, transaction_index: TxnIndex) -> Self {
220+
Self {
221+
purpose: None,
222+
txn_inputs_hash: None,
223+
prv_tx_id: None,
224+
metadata: None,
225+
validation_signature: None,
226+
report,
227+
slot,
228+
transaction_index,
229+
}
230+
}
115231

116232
/// Basic validation for CIP509
117233
/// The validation include the following:
@@ -173,50 +289,63 @@ impl Cip509 {
173289

174290
/// Returns all role numbers present in this `Cip509` instance.
175291
pub fn all_roles(&self) -> Vec<RoleNumber> {
176-
self.metadata
177-
.map(|m| m.role_data.keys())
178-
.iter()
179-
.flatten()
180-
.collect()
292+
if let Some(metadata) = &self.metadata {
293+
metadata.role_data.keys().cloned().collect()
294+
} else {
295+
Vec::new()
296+
}
181297
}
182298

183299
/// Returns a role data for the given role if it is present.
184300
pub fn role_data(&self, role: RoleNumber) -> Option<&RoleData> {
185-
self.metadata.and_then(|m| m.role_data.get(&role))
301+
self.metadata.as_ref().and_then(|m| m.role_data.get(&role))
186302
}
187303

188-
// TODO: FIXME: Consume fields in the registration chain, use methods everywhere else.
189-
// /// Returns a registration purpose.
190-
// #[must_use]
191-
// pub fn purpose(&self) -> Option<Uuid> {
192-
// self.purpose
193-
// }
194-
//
195-
// // TODO: FIXME:
196-
// // txn_inputs_hash?
197-
//
198-
// /// Returns an identifier of the previous transaction.
199-
// #[must_use]
200-
// pub fn previous_transaction(&self) -> Option<&Blake2b256Hash> {
201-
// self.prv_tx_id.as_ref()
202-
// }
203-
//
204-
// /// Returns CIP509 metadata.
205-
// #[must_use]
206-
// pub fn metadata(&self) -> Option<&Cip509RbacMetadata> {
207-
// self.metadata.as_ref()
208-
// }
209-
//
210-
// // TODO: FIXME:
211-
// // validation_signature?
304+
/// Returns a hash of the previous transaction.
305+
pub fn previous_transaction(&self) -> Option<Blake2b256Hash> {
306+
self.prv_tx_id
307+
}
308+
309+
/// Returns a problem report
310+
pub fn report(&self) -> &ProblemReport {
311+
&self.report
312+
}
313+
314+
/// Returns a slot and a transaction index where this data is originating from.
315+
pub fn origin(&self) -> (Slot, TxnIndex) {
316+
(self.slot, self.transaction_index)
317+
}
318+
319+
/// Returns `Cip509` fields consuming the structure if it was successfully decoded and
320+
/// validated otherwise return the problem report that contains all the encountered
321+
/// issues.
322+
pub fn try_consume(
323+
self,
324+
) -> Result<(Uuid, TxInputHash, Cip509RbacMetadata, ValidationSignature), ProblemReport> {
325+
match (
326+
self.purpose,
327+
self.txn_inputs_hash,
328+
self.metadata,
329+
self.validation_signature,
330+
) {
331+
(Some(purpose), Some(txn_inputs_hash), Some(metadata), Some(validation_signature))
332+
if !self.report.is_problematic() =>
333+
{
334+
Ok((purpose, txn_inputs_hash, metadata, validation_signature))
335+
},
336+
337+
_ => Err(self.report),
338+
}
339+
}
212340
}
213341

214342
impl Decode<'_, ProblemReport> for Cip509 {
215343
fn decode(d: &mut Decoder, report: &mut ProblemReport) -> Result<Self, decode::Error> {
216344
let context = "Cip509";
217345
let map_len = decode_map_len(d, context)?;
218346

219-
let mut result = Self::default();
347+
let mut result = Self::with_report(report.clone());
348+
220349
let mut found_keys = Vec::new();
221350
let mut is_metadata_found = false;
222351

0 commit comments

Comments
 (0)