Skip to content

Commit

Permalink
Add timestamp field to Block
Browse files Browse the repository at this point in the history
The `Block` now has a `timestamp` field. This field is meant to
represent a consensed timestamp for the block that all the nodes will
sign.

Currently the timestamp is *not* being populated. The logic to properly
set the timestamp is expected to come in a follow on PR.
  • Loading branch information
nick-mobilecoin committed Nov 20, 2023
1 parent 8544735 commit 84dd8db
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 30 deletions.
3 changes: 3 additions & 0 deletions api/proto/blockchain.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ message Block {

// Hash of the block's contents.
BlockContentsHash contents_hash = 7;

// The block's timestamp. ms since Unix epoch
uint64 timestamp = 8;
}

message BlockContents {
Expand Down
7 changes: 7 additions & 0 deletions api/src/convert/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl From<&Block> for blockchain::Block {
block.set_cumulative_txo_count(other.cumulative_txo_count);
block.set_root_element((&other.root_element).into());
block.set_contents_hash(blockchain::BlockContentsHash::from(&other.contents_hash));
block.set_timestamp(other.timestamp);
block
}
}
Expand All @@ -39,6 +40,7 @@ impl TryFrom<&blockchain::Block> for Block {
cumulative_txo_count: value.cumulative_txo_count,
root_element,
contents_hash,
timestamp: value.timestamp,
};
Ok(block)
}
Expand All @@ -65,6 +67,7 @@ mod tests {
hash: TxOutMembershipHash::from([12u8; 32]),
},
contents_hash: BlockContentsHash::try_from(&[66u8; 32][..]).unwrap(),
timestamp: 357,
};

let block = blockchain::Block::from(&source_block);
Expand All @@ -77,6 +80,7 @@ mod tests {
assert_eq!(block.get_root_element().get_range().get_to(), 20);
assert_eq!(block.get_root_element().get_hash().get_data(), &[12u8; 32]);
assert_eq!(block.get_contents_hash().get_data(), [66u8; 32]);
assert_eq!(block.get_timestamp(), 357);
}

#[test]
Expand All @@ -103,6 +107,7 @@ mod tests {
source_block.set_index(2);
source_block.set_root_element(root_element);
source_block.set_contents_hash(contents_hash);
source_block.set_timestamp(411);

let block = Block::try_from(&source_block).unwrap();
assert_eq!(block.id.as_ref(), [10u8; 32]);
Expand All @@ -113,6 +118,7 @@ mod tests {
assert_eq!(block.root_element.range.to, 20);
assert_eq!(block.root_element.hash.as_ref(), &[13u8; 32]);
assert_eq!(block.contents_hash.as_ref(), [66u8; 32]);
assert_eq!(block.timestamp, 411);
}

#[test]
Expand All @@ -131,6 +137,7 @@ mod tests {
hash: TxOutMembershipHash::from([12u8; 32]),
},
contents_hash: BlockContentsHash::try_from(&[66u8; 32][..]).unwrap(),
timestamp: 911,
};

// Encode using `protobuf`, decode using `prost`.
Expand Down
14 changes: 13 additions & 1 deletion blockchain/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,18 @@ pub fn get_blocks_with_recipients<R: RngCore + CryptoRng>(

let block = match &prev_block {
Some(parent) => {
Block::new_with_parent(block_version, parent, &Default::default(), &block_contents)
let timestamp = if block_version.timestamps_are_supported() {
parent.timestamp + 1
} else {
0
};
Block::new_with_parent(
block_version,
parent,
&Default::default(),
&block_contents,
timestamp,
)
}
None => Block::new_origin_block(&block_contents.outputs),
};
Expand Down Expand Up @@ -242,6 +253,7 @@ mod tests {
block.cumulative_txo_count,
&block.root_element,
&block.contents_hash,
block.timestamp,
);
assert_eq!(derived_block_id, block.id);

Expand Down
140 changes: 132 additions & 8 deletions blockchain/types/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ pub struct Block {
/// Hash of the block's contents.
#[prost(message, required, tag = "7")]
pub contents_hash: BlockContentsHash,

/// Timestamp of the block. ms since Unix epoch
#[prost(uint64, tag = "8")]
#[digestible(omit_when = 0)]
pub timestamp: u64,
}

impl Block {
Expand All @@ -58,6 +63,7 @@ impl Block {
let index: BlockIndex = 0;
let cumulative_txo_count = outputs.len() as u64;
let root_element = TxOutMembershipElement::default();
let timestamp = 0;

// The origin block does not contain anything but TxOuts.
let block_contents = BlockContents {
Expand All @@ -73,6 +79,7 @@ impl Block {
cumulative_txo_count,
&root_element,
&contents_hash,
timestamp,
);
Self {
id,
Expand All @@ -82,6 +89,7 @@ impl Block {
cumulative_txo_count,
root_element,
contents_hash,
timestamp,
}
}

Expand All @@ -100,6 +108,7 @@ impl Block {
parent: &Block,
root_element: &TxOutMembershipElement,
block_contents: &BlockContents,
timestamp: u64,
) -> Self {
Block::new(
version,
Expand All @@ -108,6 +117,7 @@ impl Block {
parent.cumulative_txo_count + block_contents.outputs.len() as u64,
root_element,
block_contents,
timestamp,
)
}

Expand All @@ -123,13 +133,15 @@ impl Block {
/// block*
/// * `root_element` - The root element for membership proofs
/// * `block_contents` - Contents of the block.
/// * `timestamp` - The timestamp of the block in ms.
pub fn new(
version: BlockVersion,
parent_id: &BlockID,
index: BlockIndex,
cumulative_txo_count: u64,
root_element: &TxOutMembershipElement,
block_contents: &BlockContents,
timestamp: u64,
) -> Self {
let contents_hash = block_contents.hash();
let id = compute_block_id(
Expand All @@ -139,6 +151,7 @@ impl Block {
cumulative_txo_count,
root_element,
&contents_hash,
timestamp,
);

Self {
Expand All @@ -149,6 +162,7 @@ impl Block {
cumulative_txo_count,
root_element: root_element.clone(),
contents_hash,
timestamp,
}
}

Expand All @@ -165,6 +179,7 @@ impl Block {
self.cumulative_txo_count,
&self.root_element,
&self.contents_hash,
self.timestamp,
);

self.id == expected_id
Expand All @@ -182,6 +197,7 @@ pub fn compute_block_id(
cumulative_txo_count: u64,
root_element: &TxOutMembershipElement,
contents_hash: &BlockContentsHash,
timestamp: u64,
) -> BlockID {
let mut transcript = MerlinTranscript::new(b"mobilecoin-block-id");

Expand All @@ -192,6 +208,12 @@ pub fn compute_block_id(
root_element.append_to_transcript(b"root_element", &mut transcript);
contents_hash.append_to_transcript(b"contents_hash", &mut transcript);

let timestamps_supported =
BlockVersion::try_from(version).map(|v| v.timestamps_are_supported());
if timestamps_supported.unwrap_or_default() {
timestamp.append_to_transcript(b"timestamp", &mut transcript);
}

let mut result = [0u8; 32];
transcript.extract_digest(&mut result);

Expand Down Expand Up @@ -252,7 +274,7 @@ mod block_tests {
(key_images, outputs)
}

fn get_block<RNG: CryptoRng + RngCore>(rng: &mut RNG) -> Block {
fn get_block_version_1<RNG: CryptoRng + RngCore>(rng: &mut RNG) -> Block {
let bytes = [14u8; 32];
let parent_id = BlockID::try_from(&bytes[..]).unwrap();

Expand All @@ -270,6 +292,29 @@ mod block_tests {
400,
&root_element,
&block_contents,
0, // timestamp of 0 for earlier block versions
)
}

fn get_block_version_4<RNG: CryptoRng + RngCore>(rng: &mut RNG) -> Block {
let bytes = [14u8; 32];
let parent_id = BlockID::try_from(&bytes[..]).unwrap();

let root_element = TxOutMembershipElement {
range: Range::new(0, 15).unwrap(),
hash: TxOutMembershipHash::from([0u8; 32]),
};

let block_contents = get_block_contents(rng);

Block::new(
BlockVersion::FOUR,
&parent_id,
3,
400,
&root_element,
&block_contents,
10,
)
}

Expand Down Expand Up @@ -298,22 +343,23 @@ mod block_tests {
400,
&root_element,
&block_contents,
0,
)
}

#[test]
/// The block returned by `get_block` should have a valid BlockID.
fn test_get_block_has_valid_id() {
let mut rng = get_seeded_rng();
let block = get_block(&mut rng);
let block = get_block_version_1(&mut rng);
assert!(block.is_block_id_valid());
}

#[test]
/// The block ID should depend on the block version.
fn test_block_id_includes_version() {
let mut rng = get_seeded_rng();
let mut block = get_block(&mut rng);
let mut block = get_block_version_1(&mut rng);
block.version += 1;
assert!(!block.is_block_id_valid());
}
Expand All @@ -322,7 +368,7 @@ mod block_tests {
/// The block ID should depend on the parent_id.
fn test_block_id_includes_parent_id() {
let mut rng = get_seeded_rng();
let mut block = get_block(&mut rng);
let mut block = get_block_version_1(&mut rng);

let mut bytes = [0u8; 32];
rng.fill_bytes(&mut bytes);
Expand All @@ -336,7 +382,7 @@ mod block_tests {
/// The block ID should depend on the block's index.
fn test_block_id_includes_block_index() {
let mut rng = get_seeded_rng();
let mut block = get_block(&mut rng);
let mut block = get_block_version_1(&mut rng);
block.index += 1;
assert!(!block.is_block_id_valid());
}
Expand All @@ -345,7 +391,7 @@ mod block_tests {
/// The block ID should depend on the root element.
fn test_block_id_includes_root_element() {
let mut rng = get_seeded_rng();
let mut block = get_block(&mut rng);
let mut block = get_block_version_1(&mut rng);

let wrong_root_element = TxOutMembershipElement {
range: Range::new(13, 17).unwrap(),
Expand All @@ -359,7 +405,7 @@ mod block_tests {
/// The block ID should depend on the content_hash.
fn test_block_id_includes_content_hash() {
let mut rng = get_seeded_rng();
let mut block = get_block(&mut rng);
let mut block = get_block_version_1(&mut rng);

let mut bytes = [0u8; 32];
rng.fill_bytes(&mut bytes);
Expand All @@ -386,7 +432,7 @@ mod block_tests {
let mut rng = get_seeded_rng();

//Check hash with memo
let block = get_block(&mut rng);
let block = get_block_version_1(&mut rng);
assert_eq!(
block.id.as_ref(),
&[
Expand Down Expand Up @@ -422,4 +468,82 @@ mod block_tests {
]
);
}
#[test]
/// The block ID hash do not change as the code evolves.
/// This test was written by writing a failed assert and then copying the
/// actual block id into the test. This should hopefully catches cases where
/// we add/change Block/BlockContents and accidentally break id
/// calculation of old blocks.
fn test_hashing_is_consistent_block_version_four() {
let mut rng = get_seeded_rng();

let block = get_block_version_4(&mut rng);
assert_eq!(
block.id.as_ref(),
&[
156, 155, 244, 98, 84, 234, 204, 146, 224, 142, 236, 197, 11, 69, 5, 74, 109, 160,
123, 173, 206, 100, 224, 171, 72, 35, 208, 137, 150, 168, 43, 93
]
);
}

#[test]
fn test_block_version_3_ignores_timestamp_in_id() {
let parent_id = BlockID::try_from(&[1u8; 32][..]).unwrap();
let index = 1;
let cumulative_txo_count = 1;
let root_element = TxOutMembershipElement::default();
let contents_hash = BlockContentsHash::default();
let timestamp_1 = 1;
let id_1 = compute_block_id(
3,
&parent_id,
index,
cumulative_txo_count,
&root_element,
&contents_hash,
timestamp_1,
);
let timestamp_2 = 2;
let id_2 = compute_block_id(
3,
&parent_id,
index,
cumulative_txo_count,
&root_element,
&contents_hash,
timestamp_2,
);
assert_eq!(id_1, id_2);
}

#[test]
fn test_block_version_4_takes_timestamp_into_account() {
let parent_id = BlockID::try_from(&[1u8; 32][..]).unwrap();
let index = 1;
let cumulative_txo_count = 1;
let root_element = TxOutMembershipElement::default();
let contents_hash = BlockContentsHash::default();
let timestamp_1 = 1;
let id_1 = compute_block_id(
4,
&parent_id,
index,
cumulative_txo_count,
&root_element,
&contents_hash,
timestamp_1,
);
let timestamp_2 = 2;
let id_2 = compute_block_id(
4,
&parent_id,
index,
cumulative_txo_count,
&root_element,
&contents_hash,
timestamp_2,
);
assert_ne!(id_1, id_2);
}
}
Loading

0 comments on commit 84dd8db

Please sign in to comment.