Skip to content

Commit 1dbe1af

Browse files
committed
feat(chain): review changes
1 parent 4c69f85 commit 1dbe1af

File tree

5 files changed

+143
-71
lines changed

5 files changed

+143
-71
lines changed

nomos-core/chain-defs/src/block/mod.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ::serde::{de::DeserializeOwned, Deserialize, Serialize};
22
use bytes::Bytes;
3-
use ed25519_dalek::{ed25519::signature::Signer as _, SigningKey};
3+
use ed25519_dalek::SigningKey;
44
use groth16::Fr;
55

66
use crate::{header::Header, mantle::Transaction};
@@ -11,10 +11,12 @@ pub type TxHash = [u8; 32];
1111
pub type BlockNumber = u64;
1212
pub type SessionNumber = u64;
1313

14-
#[derive(Debug, thiserror::Error)]
14+
#[derive(Clone, Debug, thiserror::Error)]
1515
pub enum Error {
1616
#[error("Failed to serialize: {0}")]
1717
Serialisation(#[from] crate::wire::Error),
18+
#[error("Signing error: {0}")]
19+
Signing(String),
1820
}
1921

2022
/// A block proposal
@@ -35,8 +37,9 @@ pub struct References {
3537
#[derive(Clone, Debug)]
3638
pub struct Block<Tx> {
3739
header: Header,
40+
signature: ed25519_dalek::Signature,
41+
service_reward: Option<Fr>,
3842
transactions: Vec<Tx>,
39-
pub service_reward: Option<Fr>,
4043
}
4144

4245
impl Proposal {
@@ -58,24 +61,17 @@ impl Proposal {
5861

5962
impl<Tx> Block<Tx> {
6063
#[must_use]
61-
pub const fn new(header: Header, transactions: Vec<Tx>) -> Self {
62-
Self {
63-
header,
64-
transactions,
65-
service_reward: None,
66-
}
67-
}
68-
69-
#[must_use]
70-
pub const fn new_with_service_reward(
64+
pub const fn new(
7165
header: Header,
7266
transactions: Vec<Tx>,
73-
service_reward: Fr,
67+
service_reward: Option<Fr>,
68+
signature: ed25519_dalek::Signature,
7469
) -> Self {
7570
Self {
7671
header,
72+
signature,
73+
service_reward,
7774
transactions,
78-
service_reward: Some(service_reward),
7975
}
8076
}
8177

@@ -94,6 +90,37 @@ impl<Tx> Block<Tx> {
9490
self.transactions
9591
}
9692

93+
#[must_use]
94+
pub const fn service_reward(&self) -> Option<Fr> {
95+
self.service_reward
96+
}
97+
98+
#[must_use]
99+
pub const fn signature(&self) -> &ed25519_dalek::Signature {
100+
&self.signature
101+
}
102+
103+
/// Basic validation
104+
pub fn validate(&self) -> Result<(), Error>
105+
where
106+
Tx: Transaction,
107+
Tx::Hash: Into<Fr>,
108+
{
109+
if !self.header.is_valid_bedrock_version() {
110+
return Err(Error::Signing("Invalid header version".to_owned()));
111+
}
112+
113+
if self.transactions.len() > 1024 {
114+
return Err(Error::Signing(
115+
"Too many transactions (max 1024)".to_owned(),
116+
));
117+
}
118+
119+
// TODO: validate signature?
120+
121+
Ok(())
122+
}
123+
97124
pub fn to_proposal(&self, signing_key: &SigningKey) -> Result<Proposal, Error>
98125
where
99126
Tx: Transaction,
@@ -110,8 +137,7 @@ impl<Tx> Block<Tx> {
110137
mempool_transactions,
111138
};
112139

113-
let header_bytes = crate::wire::serialize(&self.header)?;
114-
let signature = signing_key.sign(&header_bytes);
140+
let signature = self.header.sign(signing_key)?;
115141

116142
Ok(Proposal {
117143
header: self.header.clone(),

nomos-core/chain-defs/src/block/wire.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ impl<'de> Deserialize<'de> for References {
7373
#[derive(serde::Serialize, serde::Deserialize)]
7474
struct WireBlock<Tx> {
7575
header: Header,
76-
transactions: Vec<Tx>,
76+
signature: ed25519_dalek::Signature,
7777
service_reward: Option<SerializableFr>,
78+
transactions: Vec<Tx>,
7879
}
7980

8081
impl<Tx: Serialize + Clone> Serialize for Block<Tx> {
@@ -86,8 +87,9 @@ impl<Tx: Serialize + Clone> Serialize for Block<Tx> {
8687

8788
let wire = WireBlock {
8889
header: self.header.clone(),
89-
transactions: self.transactions.clone(),
90+
signature: self.signature,
9091
service_reward,
92+
transactions: self.transactions.clone(),
9193
};
9294

9395
wire.serialize(serializer)
@@ -105,14 +107,17 @@ impl<'de, Tx: Deserialize<'de>> Deserialize<'de> for Block<Tx> {
105107

106108
Ok(Self {
107109
header: wire.header,
108-
transactions: wire.transactions,
110+
signature: wire.signature,
109111
service_reward,
112+
transactions: wire.transactions,
110113
})
111114
}
112115
}
113116

114117
#[cfg(test)]
115118
mod tests {
119+
use ed25519_dalek::SigningKey;
120+
116121
use super::Fr;
117122
use crate::block::{Block, References};
118123

@@ -186,7 +191,12 @@ mod tests {
186191
let transactions = vec!["tx1".to_owned(), "tx2".to_owned()];
187192
let service_reward = Fr::from(123u64);
188193

189-
let block = Block::new_with_service_reward(header, transactions, service_reward);
194+
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
195+
let signature = header
196+
.sign(&signing_key)
197+
.expect("Header signing should work in test");
198+
199+
let block = Block::new(header, transactions, Some(service_reward), signature);
190200

191201
let wire_bytes = crate::wire::serialize(&block).expect("Failed to serialize block");
192202
let deserialized: Block<String> =

nomos-core/chain-defs/src/header/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use blake2::Digest as _;
22
use cryptarchia_engine::Slot;
3+
use ed25519_dalek::Signer as _;
34
use serde::{Deserialize, Serialize};
45

56
pub const BEDROCK_VERSION: u8 = 1;
@@ -103,6 +104,14 @@ impl Header {
103104
self.slot
104105
}
105106

107+
pub fn sign(
108+
&self,
109+
signing_key: &ed25519_dalek::SigningKey,
110+
) -> Result<ed25519_dalek::Signature, crate::block::Error> {
111+
let header_bytes = crate::wire::serialize(self)?;
112+
Ok(signing_key.sign(&header_bytes))
113+
}
114+
106115
#[must_use]
107116
pub fn new(
108117
parent_block: HeaderId,

nomos-services/chain-service/src/lib.rs

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ pub enum Error {
9292
Ledger(#[from] nomos_ledger::LedgerError<HeaderId>),
9393
#[error("Consensus error: {0}")]
9494
Consensus(#[from] cryptarchia_engine::Error<HeaderId>),
95-
#[error("Proposal error: {0}")]
96-
Proposal(String),
95+
#[error("Serialization error: {0}")]
96+
Serialisation(#[from] nomos_core::wire::Error),
97+
#[error("Invalid block: {0}")]
98+
InvalidBlock(String),
9799
#[error("Storage error: {0}")]
98100
Storage(String),
99101
}
@@ -764,32 +766,48 @@ where
764766
}
765767
}
766768

767-
// TODO: we maybe can keep existing orphan downloading logic but convert Block to Proposal
768769
Some(block) = orphan_downloader.next(), if orphan_downloader.should_poll() => {
769-
let header_id= block.header().id();
770+
let header_id = block.header().id();
770771
info!("Processing block from orphan downloader: {header_id:?}");
771772

772773
if cryptarchia.has_block(&block.header().id()) {
773774
continue;
774775
}
775776

776-
match Self::process_block_and_update_state(
777-
cryptarchia.clone(),
778-
&leader,
779-
block.clone(),
780-
&storage_blocks_to_remove,
781-
&relays,
782-
&self.block_subscription_sender,
783-
&self.service_resources_handle.state_updater
784-
).await {
785-
Ok((new_cryptarchia, new_storage_blocks_to_remove)) => {
786-
cryptarchia = new_cryptarchia;
787-
storage_blocks_to_remove = new_storage_blocks_to_remove;
777+
match block.validate() {
778+
Ok(()) => {
779+
Self::log_received_block(&block);
780+
781+
match Self::process_block_and_update_state(
782+
cryptarchia.clone(),
783+
&leader,
784+
block,
785+
&storage_blocks_to_remove,
786+
&relays,
787+
&self.block_subscription_sender,
788+
&self.service_resources_handle.state_updater
789+
).await {
790+
Ok((new_cryptarchia, new_storage_blocks_to_remove)) => {
791+
cryptarchia = new_cryptarchia;
792+
storage_blocks_to_remove = new_storage_blocks_to_remove;
793+
794+
orphan_downloader.remove_orphan(&header_id);
795+
info!(counter.consensus_processed_blocks = 1);
796+
}
797+
Err(Error::Ledger(nomos_ledger::LedgerError::ParentNotFound(parent))
798+
| Error::Consensus(cryptarchia_engine::Error::ParentMissing(parent))) => {
788799

789-
info!(counter.consensus_processed_blocks = 1);
800+
orphan_downloader.enqueue_orphan(header_id, cryptarchia.tip(), cryptarchia.lib());
801+
error!(target: LOG_TARGET, "Orphan block with parent {:?} not in ledger state", parent);
802+
}
803+
Err(e) => {
804+
error!(target: LOG_TARGET, "Error processing orphan block: {e:?}");
805+
orphan_downloader.cancel_active_download();
806+
}
807+
}
790808
}
791809
Err(e) => {
792-
error!(target: LOG_TARGET, "Error processing orphan downloader block: {e:?}");
810+
error!(target: LOG_TARGET, "Failed to validate orphan block: {e:?}");
793811
orphan_downloader.cancel_active_download();
794812
}
795813
}
@@ -1139,7 +1157,15 @@ where
11391157
.collect::<Vec<_>>();
11401158
let content_id = [0; 32].into(); // TODO: calculate the actual content id
11411159
// TODO: this should probably be a proposal or be transformed into a proposal
1142-
let block = Block::new(Header::new(parent, content_id, slot, proof), txs);
1160+
// TODO: Use correct derived one time key
1161+
let dummy_signing_key = SigningKey::from_bytes(&[1u8; 32]);
1162+
let header = Header::new(parent, content_id, slot, proof);
1163+
1164+
let Ok(signature) = header.sign(&dummy_signing_key) else {
1165+
error!("Failed to sign header during block proposal");
1166+
return None;
1167+
};
1168+
let block = Block::new(header, txs, None, signature);
11431169
debug!("proposed block with id {:?}", block.header().id());
11441170
output = Some(block);
11451171
}
@@ -1615,10 +1641,10 @@ where
16151641
BlendService::BroadcastSettings: Clone + Sync,
16161642
{
16171643
// TODO: Use correct derived one time key
1618-
let signing_key = SigningKey::generate(&mut rand::thread_rng());
1644+
let signing_key = SigningKey::from_bytes(&rand::random::<[u8; 32]>());
16191645
let proposal = block
16201646
.to_proposal(&signing_key)
1621-
.map_err(|e| Error::Proposal(format!("Failed to create proposal from block: {e}")))?;
1647+
.map_err(|e| Error::InvalidBlock(format!("Failed to create proposal from block: {e}")))?;
16221648

16231649
blend_adapter.publish_proposal(proposal).await;
16241650

@@ -1634,22 +1660,6 @@ where
16341660
Payload: Send,
16351661
Item: Clone + Transaction<Hash = TxHash> + Send,
16361662
{
1637-
if !proposal.header().is_valid_bedrock_version() {
1638-
return Err(Error::Proposal(format!(
1639-
"Invalid header version: expected all fields = 0x01, got {:?}",
1640-
proposal.header().version()
1641-
)));
1642-
}
1643-
1644-
if proposal.references().mempool_transactions.len() > 1024 {
1645-
return Err(Error::Proposal(
1646-
"Too many transaction references (max 1024)".into(),
1647-
));
1648-
}
1649-
1650-
// TODO: the rest of validations should probably go to
1651-
// process_block_and_update_state
1652-
16531663
let needed_hashes: Vec<TxHash> = proposal
16541664
.references()
16551665
.mempool_transactions
@@ -1659,7 +1669,7 @@ where
16591669

16601670
let reconstructed_transactions = get_transactions_by_hashes(mempool, needed_hashes.clone())
16611671
.await
1662-
.map_err(|e| Error::Proposal(format!("Failed to get transactions by hashes: {e}")))?;
1672+
.map_err(|e| Error::InvalidBlock(format!("Failed to get transactions by hashes: {e}")))?;
16631673

16641674
if reconstructed_transactions.len() != needed_hashes.len() {
16651675
let missing_transactions: Vec<TxHash> = needed_hashes
@@ -1671,18 +1681,26 @@ where
16711681
})
16721682
.collect();
16731683

1674-
return Err(Error::Proposal(format!(
1684+
return Err(Error::InvalidBlock(format!(
16751685
"Failed to reconstruct block: {missing_transactions:?} transactions not found in mempool",
16761686
)));
16771687
}
16781688

16791689
let header = proposal.header().clone();
16801690
let service_reward = proposal.references().service_reward;
1681-
1682-
Ok(match service_reward {
1683-
Some(reward) => Block::new_with_service_reward(header, reconstructed_transactions, reward),
1684-
None => Block::new(header, reconstructed_transactions),
1685-
})
1691+
let signature = *proposal.signature();
1692+
1693+
let block = Block::new(
1694+
header,
1695+
reconstructed_transactions,
1696+
service_reward,
1697+
signature,
1698+
);
1699+
block
1700+
.validate()
1701+
.map_err(|e| Error::InvalidBlock(format!("Reconstructed block is invalid: {e}")))?;
1702+
1703+
Ok(block)
16861704
}
16871705

16881706
#[cfg(test)]

nomos-services/chain-service/src/sync/block_provider.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,10 @@ mod tests {
750750

751751
for i in 0..count {
752752
let slot = Slot::from(slot_offset + i as u64);
753-
let block = self.build_block_with_parent(prev_header, slot);
753+
let Some(block) = self.build_block_with_parent(prev_header, slot) else {
754+
error!("Failed to build block with parent");
755+
break;
756+
};
754757
let header_id = block.header().id();
755758

756759
blocks.push((block, header_id, prev_header, slot));
@@ -813,11 +816,17 @@ mod tests {
813816
&self,
814817
prev_header: HeaderId,
815818
slot: Slot,
816-
) -> Block<SignedMantleTx> {
817-
Block::new(
818-
Header::new(prev_header, [0; 32].into(), slot, self.proof.clone()),
819-
vec![],
820-
)
819+
) -> Option<Block<SignedMantleTx>> {
820+
// TODO: Use correct derived one time key
821+
let dummy_signing_key = ed25519_dalek::SigningKey::from_bytes(&[1u8; 32]);
822+
let header = Header::new(prev_header, [0; 32].into(), slot, self.proof.clone());
823+
824+
let Ok(signature) = header.sign(&dummy_signing_key) else {
825+
error!("Failed to sign header in block provider");
826+
return None;
827+
};
828+
829+
Some(Block::new(header, vec![], None, signature))
821830
}
822831

823832
async fn add_block(

0 commit comments

Comments
 (0)