Skip to content

Commit 672e2d7

Browse files
committed
feat(block validation): ledger
1 parent 609a99b commit 672e2d7

File tree

3 files changed

+299
-12
lines changed

3 files changed

+299
-12
lines changed

rust/immutable-ledger/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Block Serialization
22
//!
3-
//! Facilitates block serializatio and validation for immutable ledger
3+
//! Facilitates block serialization and validation for immutable ledger
4+
//!
5+
//! Spec: https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/immutable_ledger/ledger/
6+
//!
47
58
/// Block validation logic
69
pub mod validate;

rust/immutable-ledger/src/serialize.rs

Lines changed: 217 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,21 @@ pub struct DecodedBlockData(Vec<u8>);
6363
#[derive(Debug, Clone, PartialEq)]
6464
pub struct EncodedBlockData(pub Vec<u8>);
6565

66+
/// Encoded genesis Block contents as cbor, used for hash validation
67+
#[derive(Debug, Clone, PartialEq)]
68+
pub struct EncodedGenesisBlockContents(pub Vec<u8>);
69+
6670
/// Signatures
6771
#[derive(Debug, Clone, PartialEq)]
6872
pub struct Signatures(Vec<Signature>);
6973

7074
/// Validator's keys defined in the corresponding certificates referenced by the validator.
7175
pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>);
7276

73-
/// Decoder block
77+
/// Decoded block
7478
pub type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures);
7579

76-
/// Decoder block header
80+
/// Decoded block header
7781
pub type DecodedBlockHeader = (
7882
ChainId,
7983
Height,
@@ -86,9 +90,25 @@ pub type DecodedBlockHeader = (
8690
BlockHeaderSize,
8791
);
8892

93+
/// Decoded Genesis block
94+
pub type DecodedBlockGenesis = (
95+
ChainId,
96+
Height,
97+
BlockTimeStamp,
98+
PreviousBlockHash,
99+
LedgerType,
100+
PurposeId,
101+
Validator,
102+
BlockHeaderSize,
103+
EncodedGenesisBlockContents,
104+
);
105+
89106
/// Encoded whole block including block header, cbor encoded block data and signatures.
90107
pub type EncodedBlock = Vec<u8>;
91108

109+
/// Encoded genesis block, see genesis_to_prev_hash
110+
pub type EncodedGenesisBlock = Vec<u8>;
111+
92112
/// Choice of hash function:
93113
/// must be the same as the hash of the previous block.
94114
pub enum HashFunction {
@@ -98,7 +118,7 @@ pub enum HashFunction {
98118
Blake2b,
99119
}
100120

101-
/// Encode block
121+
/// Encode standard block
102122
pub fn encode_block(
103123
block_hdr_cbor: Vec<u8>, block_data: EncodedBlockData, validator_keys: ValidatorKeys,
104124
hasher: HashFunction,
@@ -134,13 +154,15 @@ pub fn encode_block(
134154
encoder.bytes(sig)?;
135155
}
136156

137-
Ok([block_hdr_cbor, encoder.writer().to_vec()].concat())
157+
let block_data_with_sigs = encoder.writer().to_vec();
158+
// block hdr + block data + sigs
159+
let encoded_block = [block_hdr_cbor, block_data_with_sigs].concat();
160+
161+
Ok(encoded_block)
138162
}
139163

140-
/// Decoded block
164+
/// Decoded standard block
141165
pub fn decode_block(encoded_block: Vec<u8>) -> anyhow::Result<DecodedBlock> {
142-
// Decode cbor to bytes
143-
144166
// Decoded block hdr
145167
let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?;
146168

@@ -211,6 +233,7 @@ pub fn encode_block_header(
211233

212234
Ok(encoder.writer().to_vec())
213235
}
236+
214237
/// Decode block header
215238
pub fn decode_block_header(block: Vec<u8>) -> anyhow::Result<DecodedBlockHeader> {
216239
// Decode cbor to bytes
@@ -301,6 +324,146 @@ pub fn decode_block_header(block: Vec<u8>) -> anyhow::Result<DecodedBlockHeader>
301324
))
302325
}
303326

327+
/// Encode genesis block
328+
pub fn encode_genesis(
329+
chain_id: ChainId, ts: BlockTimeStamp, ledger_type: LedgerType, pid: PurposeId,
330+
validator: Validator, hasher: HashFunction,
331+
) -> anyhow::Result<Vec<u8>> {
332+
// Genesis block MUST have 0 value
333+
const BLOCK_HEIGHT: u32 = 0;
334+
335+
let out: Vec<u8> = Vec::new();
336+
let mut encoder = minicbor::Encoder::new(out);
337+
338+
encoder.bytes(&chain_id.0.to_bytes())?;
339+
encoder.bytes(&BLOCK_HEIGHT.to_be_bytes())?;
340+
encoder.bytes(&ts.0.to_be_bytes())?;
341+
encoder.bytes(ledger_type.0.as_bytes())?;
342+
encoder.bytes(&pid.0.to_bytes())?;
343+
344+
// marks how many validators for decoding side.
345+
encoder.bytes(&validator.0.len().to_be_bytes())?;
346+
for validator in validator.0.iter() {
347+
encoder.bytes(&validator.0)?;
348+
}
349+
350+
// Get hash of the genesis_to_prev_hash bytes i.e hash of itself
351+
let genesis_prev_bytes = encoder.writer().to_vec();
352+
353+
// Size of encoded contents which is hashed
354+
encoder.bytes(&genesis_prev_bytes.len().to_be_bytes())?;
355+
356+
let genesis_prev_hash = match hasher {
357+
HashFunction::Blake3 => blake3(&genesis_prev_bytes)?.to_vec(),
358+
HashFunction::Blake2b => blake2b_512(&genesis_prev_bytes)?.to_vec(),
359+
};
360+
361+
// prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes
362+
// last 64 bytes (depending on given hash function) of encoding are the hash of the genesis contents
363+
encoder.bytes(&genesis_prev_hash.as_slice())?;
364+
365+
Ok(encoder.writer().to_vec())
366+
}
367+
368+
/// Decode genesis
369+
pub fn decode_genesis_block(genesis_block: Vec<u8>) -> anyhow::Result<DecodedBlockGenesis> {
370+
let binding = genesis_block.clone();
371+
let mut cbor_decoder = minicbor::Decoder::new(&binding);
372+
373+
// Raw chain_id
374+
let chain_id = ChainId(Ulid::from_bytes(
375+
cbor_decoder
376+
.bytes()
377+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))?
378+
.try_into()?,
379+
));
380+
381+
// Raw Block height
382+
let block_height = Height(u32::from_be_bytes(
383+
cbor_decoder
384+
.bytes()
385+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))?
386+
.try_into()?,
387+
));
388+
389+
// Raw time stamp
390+
let ts = BlockTimeStamp(i64::from_be_bytes(
391+
cbor_decoder
392+
.bytes()
393+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))?
394+
.try_into()?,
395+
));
396+
397+
// Raw ledger type
398+
let ledger_type = LedgerType(Uuid::from_bytes(
399+
cbor_decoder
400+
.bytes()
401+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))?
402+
.try_into()?,
403+
));
404+
405+
// Raw purpose id
406+
let purpose_id = PurposeId(Ulid::from_bytes(
407+
cbor_decoder
408+
.bytes()
409+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))?
410+
.try_into()?,
411+
));
412+
413+
// Number of validators
414+
let number_of_validators = usize::from_be_bytes(
415+
cbor_decoder
416+
.bytes()
417+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))?
418+
.try_into()?,
419+
);
420+
421+
// Extract validators
422+
let mut validators = Vec::new();
423+
for _validator in 0..number_of_validators {
424+
let validator_kid: [u8; 16] = cbor_decoder
425+
.bytes()
426+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))?
427+
.try_into()?;
428+
429+
validators.push(Kid(validator_kid));
430+
}
431+
432+
// Size of encoded contents
433+
let encoded_content_size = usize::from_be_bytes(
434+
cbor_decoder
435+
.bytes()
436+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for encoded contents size : {e}")))?
437+
.try_into()?,
438+
);
439+
440+
// prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes
441+
// last 64 bytes (depending on hash function) of encoding are the hash of the contents
442+
let prev_block_hash = PreviousBlockHash(
443+
cbor_decoder
444+
.bytes()
445+
.map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))?
446+
.to_vec(),
447+
);
448+
449+
let genesis_block_contents: Vec<u8> = genesis_block
450+
.into_iter()
451+
.take(encoded_content_size)
452+
.collect();
453+
454+
Ok((
455+
chain_id,
456+
block_height,
457+
ts,
458+
prev_block_hash,
459+
ledger_type,
460+
purpose_id,
461+
Validator(validators),
462+
BlockHeaderSize(cbor_decoder.position()),
463+
EncodedGenesisBlockContents(genesis_block_contents),
464+
))
465+
}
466+
304467
#[cfg(test)]
305468
mod tests {
306469
use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH};
@@ -315,6 +478,8 @@ mod tests {
315478
};
316479

317480
use crate::serialize::HashFunction::Blake2b;
481+
482+
use super::{decode_genesis_block, encode_genesis};
318483
#[test]
319484
fn block_header_encode_decode() {
320485
let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff")
@@ -443,4 +608,49 @@ mod tests {
443608
verifying_key.verify_strict(&data_to_sign, &sig).unwrap();
444609
}
445610
}
611+
612+
#[test]
613+
fn genesis_block_encode_decode() {
614+
let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff")
615+
.unwrap()
616+
.try_into()
617+
.unwrap();
618+
619+
let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff")
620+
.unwrap()
621+
.try_into()
622+
.unwrap();
623+
624+
let chain_id = ChainId(Ulid::new());
625+
let block_ts = BlockTimeStamp(0);
626+
let ledger_type = LedgerType(Uuid::new_v4());
627+
let purpose_id = PurposeId(Ulid::new());
628+
let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]);
629+
630+
let encoded_block_genesis = encode_genesis(
631+
chain_id,
632+
block_ts,
633+
ledger_type.clone(),
634+
purpose_id.clone(),
635+
validators.clone(),
636+
Blake2b,
637+
)
638+
.unwrap();
639+
640+
let decoded_genesis = decode_genesis_block(encoded_block_genesis.clone()).unwrap();
641+
assert_eq!(decoded_genesis.0, chain_id);
642+
assert_eq!(decoded_genesis.1, Height(0));
643+
assert_eq!(decoded_genesis.2, block_ts);
644+
assert_eq!(decoded_genesis.4, ledger_type);
645+
assert_eq!(decoded_genesis.5, purpose_id);
646+
assert_eq!(decoded_genesis.6, validators);
647+
648+
// prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes
649+
let prev_block_hash = decoded_genesis.3 .0;
650+
651+
// last 64 bytes of encoding are the hash of the contents
652+
let prev_block_from_original_encoding = &encoded_block_genesis[110..];
653+
654+
assert_eq!(prev_block_hash, prev_block_from_original_encoding);
655+
}
446656
}

0 commit comments

Comments
 (0)