Skip to content

Commit 8ed1b02

Browse files
authored
Merge pull request #186 from pragma-org/jeluard/rules-validation-errors
chore: make sure rules validation returns a structured result
2 parents 8a43849 + 2e82638 commit 8ed1b02

File tree

15 files changed

+196
-153
lines changed

15 files changed

+196
-153
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ coverage/
3838

3939
# Cardano node support files
4040
/cardano-node-config/
41+
42+
# llvm-cov generated file
43+
lcov.info

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ authors = ["Amaru Maintainers <[email protected]>"]
77
repository = "https://github.com/pragma-org/amaru"
88
homepage = "https://github.com/pragma-org/amaru"
99
documentation = "https://docs.rs/amaru"
10-
rust-version = "1.84" # ⚠️ Also change in .cargo/rust-toolchain.toml
10+
rust-version = "1.88" # ⚠️ Also change in .cargo/rust-toolchain.toml
1111

1212
[workspace]
1313
members = ["crates/*", "simulation/*"]

Makefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,7 @@ build-examples: ## Build all examples
110110
@for dir in $(wildcard examples/*/.); do \
111111
if [ -f $$dir/Makefile ]; then \
112112
echo "Building $$dir"; \
113-
$(MAKE) -C $$dir; \
114-
if [ $$? != "0" ]; then \
115-
exit $$?; \
116-
fi; \
113+
$(MAKE) -C $$dir || exit; \
117114
fi; \
118115
done
119116

crates/amaru-consensus/src/consensus/chain_selection.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,10 @@ pub(crate) mod tests {
497497
chain_selector.select_roll_forward(&alice, *header);
498498
});
499499

500+
#[allow(clippy::double_ended_iterator_last)]
500501
let result = chain2
501502
.iter()
503+
// TODO looks like this test relies on the fact that `select_roll_forward` is called on every element. `map` might not be correct then
502504
.map(|header| chain_selector.select_roll_forward(&bob, *header))
503505
.last();
504506

@@ -533,7 +535,7 @@ pub(crate) mod tests {
533535
let result = chain1
534536
.iter()
535537
.map(|header| chain_selector.select_roll_forward(&alice, *header))
536-
.last();
538+
.next_back();
537539

538540
assert_eq!(ForwardChainSelection::NoChange, result.unwrap());
539541
}

crates/amaru-ledger/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#![feature(try_trait_v2)]
16+
1517
use amaru_kernel::Point;
1618
use tracing::Span;
1719

crates/amaru-ledger/src/rules.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,10 @@ pub(crate) mod tests {
128128
use super::*;
129129
use crate::{
130130
context::assert::{AssertPreparationContext, AssertValidationContext},
131-
rules,
132-
rules::block::{InvalidBlock, InvalidBlockHeader},
131+
rules::{
132+
self,
133+
block::{BlockValidation, InvalidBlockDetails},
134+
},
133135
tests::{fake_input, fake_output},
134136
};
135137
use amaru_kernel::protocol_parameters::ProtocolParameters;
@@ -174,12 +176,12 @@ pub(crate) mod tests {
174176
prepare_block(&mut ctx, &block);
175177

176178
let results = rules::block::execute(
177-
AssertValidationContext::from(ctx),
179+
&mut AssertValidationContext::from(ctx),
178180
ProtocolParameters::default(),
179-
block,
181+
&block,
180182
);
181183

182-
assert!(results.is_ok())
184+
assert!(matches!(results, BlockValidation::Valid));
183185
}
184186

185187
#[test]
@@ -200,14 +202,12 @@ pub(crate) mod tests {
200202

201203
prepare_block(&mut ctx, &block);
202204

203-
assert!(
204-
rules::block::execute(AssertValidationContext::from(ctx), pp, block).is_err_and(|e| {
205-
matches!(
206-
e,
207-
InvalidBlock::Header(InvalidBlockHeader::SizeTooBig { .. })
208-
)
209-
})
210-
)
205+
let results = rules::block::execute(&mut AssertValidationContext::from(ctx), pp, &block);
206+
207+
assert!(matches!(
208+
results,
209+
BlockValidation::Invalid(InvalidBlockDetails::HeaderSizeTooBig { .. })
210+
))
211211
}
212212

213213
macro_rules! fixture_context {

crates/amaru-ledger/src/rules/block.rs

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,76 +16,109 @@ pub mod body_size;
1616
pub mod ex_units;
1717
pub mod header_size;
1818

19-
pub use crate::rules::block::{
20-
body_size::InvalidBlockSize, ex_units::InvalidExUnits, header_size::InvalidBlockHeader,
21-
};
2219
use crate::{
2320
context::ValidationContext,
2421
rules::{transaction, transaction::InvalidTransaction},
2522
state::FailedTransactions,
2623
};
2724
use amaru_kernel::{
28-
protocol_parameters::ProtocolParameters, AuxiliaryData, HasExUnits, Hash, MintedBlock,
25+
protocol_parameters::ProtocolParameters, AuxiliaryData, ExUnits, HasExUnits, Hash, MintedBlock,
2926
OriginalHash, StakeCredential, TransactionPointer,
3027
};
31-
use std::ops::Deref;
32-
use thiserror::Error;
28+
use std::ops::{ControlFlow, Deref, FromResidual, Try};
3329
use tracing::{instrument, Level};
3430

35-
#[derive(Debug, Error)]
36-
pub enum InvalidBlock {
37-
#[error("Invalid block's size: {0}")]
38-
Size(#[from] InvalidBlockSize),
39-
40-
#[error("Invalid block's execution units: {0}")]
41-
ExUnits(#[from] InvalidExUnits),
42-
43-
#[error("Invalid block header: {0}")]
44-
Header(#[from] InvalidBlockHeader),
45-
46-
#[error(
47-
"Invalid transaction (hash: {transaction_hash}, index: {transaction_index}): {violation} "
48-
)]
31+
#[derive(Debug)]
32+
pub enum InvalidBlockDetails {
33+
BlockSizeMismatch {
34+
supplied: usize,
35+
actual: usize,
36+
},
37+
TooManyExUnits {
38+
provided: ExUnits,
39+
max: ExUnits,
40+
},
41+
HeaderSizeTooBig {
42+
supplied: usize,
43+
max: usize,
44+
},
4945
Transaction {
5046
transaction_hash: Hash<32>,
5147
transaction_index: u32,
5248
violation: InvalidTransaction,
5349
},
54-
55-
// TODO: This error shouldn't exist, it's a placeholder for better error handling in less straight forward cases
56-
#[error("Uncategorized error: {0}")]
5750
UncategorizedError(String),
5851
}
5952

53+
#[derive(Debug)]
54+
pub enum BlockValidation {
55+
Valid,
56+
Invalid(InvalidBlockDetails),
57+
}
58+
59+
impl Try for BlockValidation {
60+
type Output = ();
61+
type Residual = InvalidBlockDetails;
62+
63+
fn from_output((): Self::Output) -> Self {
64+
BlockValidation::Valid
65+
}
66+
67+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
68+
match self {
69+
BlockValidation::Valid => ControlFlow::Continue(()),
70+
BlockValidation::Invalid(e) => ControlFlow::Break(e),
71+
}
72+
}
73+
}
74+
75+
impl FromResidual for BlockValidation {
76+
fn from_residual(residual: InvalidBlockDetails) -> Self {
77+
BlockValidation::Invalid(residual)
78+
}
79+
}
80+
81+
impl From<BlockValidation> for Result<(), InvalidBlockDetails> {
82+
fn from(validation: BlockValidation) -> Self {
83+
match validation {
84+
BlockValidation::Valid => Ok(()),
85+
BlockValidation::Invalid(e) => Err(e),
86+
}
87+
}
88+
}
89+
6090
#[instrument(level = Level::TRACE, skip_all)]
6191
pub fn execute<C: ValidationContext<FinalState = S>, S: From<C>>(
62-
mut context: C,
92+
context: &mut C,
6393
protocol_params: ProtocolParameters,
64-
block: MintedBlock<'_>,
65-
) -> Result<S, InvalidBlock> {
94+
block: &MintedBlock<'_>,
95+
) -> BlockValidation {
6696
header_size::block_header_size_valid(block.header.raw_cbor(), &protocol_params)?;
6797

68-
body_size::block_body_size_valid(&block.header.header_body, &block)?;
98+
body_size::block_body_size_valid(block)?;
6999

70100
ex_units::block_ex_units_valid(block.ex_units(), &protocol_params)?;
71101

72-
let failed_transactions = FailedTransactions::from_block(&block);
102+
let failed_transactions = FailedTransactions::from_block(block);
73103

74104
let witness_sets = block.transaction_witness_sets.deref().to_vec();
75105

76-
let transactions = block.transaction_bodies.to_vec();
106+
let transactions = block.transaction_bodies.deref().to_vec();
77107

78108
// using `zip` here instead of enumerate as it is safer to cast from u32 to usize than usize to u32
79109
// Realistically, we're never gonna hit the u32 limit with the number of transactions in a block (a boy can dream)
80110
for (i, transaction) in (0u32..).zip(transactions.into_iter()) {
81111
let transaction_hash = transaction.original_hash();
82112

83-
let witness_set = witness_sets
84-
.get(i as usize)
85-
.ok_or(InvalidBlock::UncategorizedError(format!(
86-
"Missing witness set for transaction index {}",
87-
i
88-
)))?;
113+
let witness_set = match witness_sets.get(i as usize) {
114+
Some(witness_set) => witness_set,
115+
None => {
116+
return BlockValidation::Invalid(InvalidBlockDetails::UncategorizedError(format!(
117+
"Witness set not found for transaction index {}",
118+
i
119+
)));
120+
}
121+
};
89122

90123
let auxiliary_data: Option<&AuxiliaryData> = block
91124
.auxiliary_data_set
@@ -108,21 +141,22 @@ pub fn execute<C: ValidationContext<FinalState = S>, S: From<C>>(
108141
transaction_index: i as usize, // From u32
109142
};
110143

111-
transaction::execute(
112-
&mut context,
144+
if let Err(err) = transaction::execute(
145+
context,
113146
&protocol_params,
114147
pointer,
115148
!failed_transactions.has(i),
116149
transaction,
117150
witness_set,
118151
auxiliary_data,
119-
)
120-
.map_err(|err| InvalidBlock::Transaction {
121-
transaction_hash,
122-
transaction_index: i,
123-
violation: err,
124-
})?;
152+
) {
153+
return BlockValidation::Invalid(InvalidBlockDetails::Transaction {
154+
transaction_hash,
155+
transaction_index: i,
156+
violation: err,
157+
});
158+
}
125159
}
126160

127-
Ok(context.into())
161+
BlockValidation::Valid
128162
}

crates/amaru-ledger/src/rules/block/body_size.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,24 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use amaru_kernel::{to_cbor, HeaderBody, MintedBlock};
16-
use thiserror::Error;
15+
use amaru_kernel::{to_cbor, MintedBlock};
1716

18-
#[derive(Debug, Error)]
19-
pub enum InvalidBlockSize {
20-
#[error("block body size mismatch: supplied {supplied}, actual {actual}")]
21-
SizeMismatch { supplied: usize, actual: usize },
22-
}
17+
use super::{BlockValidation, InvalidBlockDetails};
2318

2419
/// This validation checks that the purported block body size matches the actual block body size.
2520
/// The validation of the bounds happens in the networking layer
26-
pub fn block_body_size_valid(
27-
block_header: &HeaderBody,
28-
block: &MintedBlock<'_>,
29-
) -> Result<(), InvalidBlockSize> {
21+
pub fn block_body_size_valid(block: &MintedBlock<'_>) -> BlockValidation {
22+
let block_header = &block.header.header_body;
3023
let bh_size = block_header.block_body_size as usize;
3124
let actual_block_size = calculate_block_body_size(block);
3225

3326
if bh_size != actual_block_size {
34-
Err(InvalidBlockSize::SizeMismatch {
27+
BlockValidation::Invalid(InvalidBlockDetails::BlockSizeMismatch {
3528
supplied: bh_size,
3629
actual: actual_block_size,
3730
})
3831
} else {
39-
Ok(())
32+
BlockValidation::Valid
4033
}
4134
}
4235

@@ -58,7 +51,7 @@ mod tests {
5851
use amaru_kernel::{include_cbor, MintedBlock};
5952
use test_case::test_case;
6053

61-
use super::InvalidBlockSize;
54+
use crate::rules::block::InvalidBlockDetails;
6255

6356
macro_rules! fixture {
6457
($number:literal) => {
@@ -70,12 +63,11 @@ mod tests {
7063
}
7164

7265
#[test_case(fixture!("2667660"); "valid")]
73-
#[test_case(fixture!("2667660", "invalid_block_body_size") =>
74-
matches Err(InvalidBlockSize::SizeMismatch {supplied, actual})
66+
#[test_case(fixture!("2667660", "invalid_block_body_size") =>
67+
matches Err(InvalidBlockDetails::BlockSizeMismatch {supplied, actual})
7568
if supplied == 0 && actual == 3411;
76-
"block body size mismatch"
77-
)]
78-
fn test_block_size(block: MintedBlock<'_>) -> Result<(), InvalidBlockSize> {
79-
super::block_body_size_valid(&block.header.header_body, &block)
69+
"block body size mismatch")]
70+
fn test_block_size(block: MintedBlock<'_>) -> Result<(), InvalidBlockDetails> {
71+
super::block_body_size_valid(&block).into()
8072
}
8173
}

0 commit comments

Comments
 (0)