Skip to content

Commit c92df19

Browse files
committed
feat(chain): review changes
1 parent cc6863a commit c92df19

File tree

6 files changed

+144
-70
lines changed

6 files changed

+144
-70
lines changed

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

Lines changed: 42 additions & 15 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::{Signer as _, SigningKey};
44
use groth16::Fr;
55

66
use crate::{codec::SerdeOp, 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::codec::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,

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
Serialize::serialize(&wire, 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::{
118123
block::{Block, References},
@@ -189,7 +194,12 @@ mod tests {
189194
let transactions = vec!["tx1".to_owned(), "tx2".to_owned()];
190195
let service_reward = Fr::from(123u64);
191196

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

194204
let wire_bytes =
195205
<Block<String> as SerdeOp>::serialize(&block).expect("Failed to serialize block");

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

Lines changed: 10 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;
@@ -9,6 +10,7 @@ use crate::{
910
proofs::leader_proof::{LeaderProof, Risc0LeaderProof},
1011
utils::{display_hex_bytes_newtype, serde_bytes_newtype},
1112
};
13+
use crate::codec::SerdeOp;
1214

1315
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, PartialOrd, Ord)]
1416
pub struct HeaderId([u8; 32]);
@@ -103,6 +105,14 @@ impl Header {
103105
self.slot
104106
}
105107

108+
pub fn sign(
109+
&self,
110+
signing_key: &ed25519_dalek::SigningKey,
111+
) -> Result<ed25519_dalek::Signature, crate::block::Error> {
112+
let header_bytes = <Self as SerdeOp>::serialize(self)?;
113+
Ok(signing_key.sign(&header_bytes))
114+
}
115+
106116
#[must_use]
107117
pub fn new(
108118
parent_block: HeaderId,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ macro_rules! serde_bytes_newtype {
3737
D: serde::Deserializer<'de>,
3838
{
3939
if deserializer.is_human_readable() {
40-
let s = <String>::deserialize(deserializer)?;
40+
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
4141
const_hex::decode_to_array(s)
4242
.map(Self)
4343
.map_err(serde::de::Error::custom)
4444
} else {
45-
<[u8; $len]>::deserialize(deserializer).map(Self)
45+
<[u8; $len] as serde::Deserialize>::deserialize(deserializer).map(Self)
4646
}
4747
}
4848
}

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

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ pub enum Error {
9494
Ledger(#[from] nomos_ledger::LedgerError<HeaderId>),
9595
#[error("Consensus error: {0}")]
9696
Consensus(#[from] cryptarchia_engine::Error<HeaderId>),
97-
#[error("Proposal error: {0}")]
98-
Proposal(String),
97+
#[error("Serialization error: {0}")]
98+
Serialisation(#[from] nomos_core::codec::Error),
99+
#[error("Invalid block: {0}")]
100+
InvalidBlock(String),
99101
#[error("Storage error: {0}")]
100102
Storage(String),
101103
}
@@ -781,31 +783,47 @@ where
781783
}
782784
}
783785

784-
// TODO: we maybe can keep existing orphan downloading logic but convert Block to Proposal
785786
Some(block) = orphan_downloader.next(), if orphan_downloader.should_poll() => {
786-
let header_id= block.header().id();
787+
let header_id = block.header().id();
787788
info!("Processing block from orphan downloader: {header_id:?}");
788789

789790
if cryptarchia.has_block(&block.header().id()) {
790791
continue;
791792
}
792793

793-
match Self::process_block_and_update_state(
794-
cryptarchia.clone(),
795-
&leader,
796-
block.clone(),
797-
&storage_blocks_to_remove,
798-
&relays,
799-
&self.service_resources_handle.state_updater
800-
).await {
801-
Ok((new_cryptarchia, new_storage_blocks_to_remove)) => {
802-
cryptarchia = new_cryptarchia;
803-
storage_blocks_to_remove = new_storage_blocks_to_remove;
794+
match block.validate() {
795+
Ok(()) => {
796+
Self::log_received_block(&block);
804797

805-
info!(counter.consensus_processed_blocks = 1);
798+
match Self::process_block_and_update_state(
799+
cryptarchia.clone(),
800+
&leader,
801+
block,
802+
&storage_blocks_to_remove,
803+
&relays,
804+
&self.service_resources_handle.state_updater
805+
).await {
806+
Ok((new_cryptarchia, new_storage_blocks_to_remove)) => {
807+
cryptarchia = new_cryptarchia;
808+
storage_blocks_to_remove = new_storage_blocks_to_remove;
809+
810+
orphan_downloader.remove_orphan(&header_id);
811+
info!(counter.consensus_processed_blocks = 1);
812+
}
813+
Err(Error::Ledger(nomos_ledger::LedgerError::ParentNotFound(parent))
814+
| Error::Consensus(cryptarchia_engine::Error::ParentMissing(parent))) => {
815+
816+
orphan_downloader.enqueue_orphan(header_id, cryptarchia.tip(), cryptarchia.lib());
817+
error!(target: LOG_TARGET, "Orphan block with parent {:?} not in ledger state", parent);
818+
}
819+
Err(e) => {
820+
error!(target: LOG_TARGET, "Error processing orphan block: {e:?}");
821+
orphan_downloader.cancel_active_download();
822+
}
823+
}
806824
}
807825
Err(e) => {
808-
error!(target: LOG_TARGET, "Error processing orphan downloader block: {e:?}");
826+
error!(target: LOG_TARGET, "Failed to validate orphan block: {e:?}");
809827
orphan_downloader.cancel_active_download();
810828
}
811829
}
@@ -1157,7 +1175,15 @@ where
11571175
.collect::<Vec<_>>();
11581176
let content_id = [0; 32].into(); // TODO: calculate the actual content id
11591177
// TODO: this should probably be a proposal or be transformed into a proposal
1160-
let block = Block::new(Header::new(parent, content_id, slot, proof), txs);
1178+
// TODO: Use correct derived one time key
1179+
let dummy_signing_key = SigningKey::from_bytes(&[1u8; 32]);
1180+
let header = Header::new(parent, content_id, slot, proof);
1181+
1182+
let Ok(signature) = header.sign(&dummy_signing_key) else {
1183+
error!("Failed to sign header during block proposal");
1184+
return None;
1185+
};
1186+
let block = Block::new(header, txs, None, signature);
11611187
debug!("proposed block with id {:?}", block.header().id());
11621188
output = Some(block);
11631189
}
@@ -1599,10 +1625,10 @@ where
15991625
BlendService::BroadcastSettings: Clone + Sync,
16001626
{
16011627
// TODO: Use correct derived one time key
1602-
let signing_key = SigningKey::generate(&mut rand::thread_rng());
1628+
let signing_key = SigningKey::from_bytes(&rand::random::<[u8; 32]>());
16031629
let proposal = block
16041630
.to_proposal(&signing_key)
1605-
.map_err(|e| Error::Proposal(format!("Failed to create proposal from block: {e}")))?;
1631+
.map_err(|e| Error::InvalidBlock(format!("Failed to create proposal from block: {e}")))?;
16061632

16071633
blend_adapter.publish_proposal(proposal).await;
16081634

@@ -1618,22 +1644,6 @@ where
16181644
Payload: Send,
16191645
Item: Clone + Transaction<Hash = TxHash> + Send,
16201646
{
1621-
if !proposal.header().is_valid_bedrock_version() {
1622-
return Err(Error::Proposal(format!(
1623-
"Invalid header version: expected all fields = 0x01, got {:?}",
1624-
proposal.header().version()
1625-
)));
1626-
}
1627-
1628-
if proposal.references().mempool_transactions.len() > 1024 {
1629-
return Err(Error::Proposal(
1630-
"Too many transaction references (max 1024)".into(),
1631-
));
1632-
}
1633-
1634-
// TODO: the rest of validations should probably go to
1635-
// process_block_and_update_state
1636-
16371647
let needed_hashes: Vec<TxHash> = proposal
16381648
.references()
16391649
.mempool_transactions
@@ -1643,7 +1653,7 @@ where
16431653

16441654
let reconstructed_transactions = get_transactions_by_hashes(mempool, needed_hashes.clone())
16451655
.await
1646-
.map_err(|e| Error::Proposal(format!("Failed to get transactions by hashes: {e}")))?;
1656+
.map_err(|e| Error::InvalidBlock(format!("Failed to get transactions by hashes: {e}")))?;
16471657

16481658
if reconstructed_transactions.len() != needed_hashes.len() {
16491659
let missing_transactions: Vec<TxHash> = needed_hashes
@@ -1655,18 +1665,26 @@ where
16551665
})
16561666
.collect();
16571667

1658-
return Err(Error::Proposal(format!(
1668+
return Err(Error::InvalidBlock(format!(
16591669
"Failed to reconstruct block: {missing_transactions:?} transactions not found in mempool",
16601670
)));
16611671
}
16621672

16631673
let header = proposal.header().clone();
16641674
let service_reward = proposal.references().service_reward;
1665-
1666-
Ok(match service_reward {
1667-
Some(reward) => Block::new_with_service_reward(header, reconstructed_transactions, reward),
1668-
None => Block::new(header, reconstructed_transactions),
1669-
})
1675+
let signature = *proposal.signature();
1676+
1677+
let block = Block::new(
1678+
header,
1679+
reconstructed_transactions,
1680+
service_reward,
1681+
signature,
1682+
);
1683+
block
1684+
.validate()
1685+
.map_err(|e| Error::InvalidBlock(format!("Reconstructed block is invalid: {e}")))?;
1686+
1687+
Ok(block)
16701688
}
16711689

16721690
async fn broadcast_finalized_block(

0 commit comments

Comments
 (0)