Skip to content

Commit

Permalink
Semantic validation cleanup (#1516)
Browse files Browse the repository at this point in the history
* Some semantic validation changes

* Avoid duplications

* Validate as context method

* Collect native_token_ids into a HashSet
  • Loading branch information
thibault-martinez authored Oct 27, 2023
1 parent 50f686c commit cbf7b9f
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 311 deletions.
14 changes: 5 additions & 9 deletions sdk/src/client/api/block_builder/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
types::block::{
output::{Output, OutputId},
payload::signed_transaction::{SignedTransactionPayload, Transaction},
semantic::{semantic_validation, TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
signature::Ed25519Signature,
BlockId, BlockWrapper,
},
Expand All @@ -36,18 +36,14 @@ pub fn verify_semantic(
.map(|input| (input.output_id(), &input.output))
.collect::<Vec<(&OutputId, &Output)>>();

let context = ValidationContext::new(
&transaction_id,
let context = SemanticValidationContext::new(
transaction_payload.transaction(),
inputs.iter().map(|(id, input)| (*id, *input)),
&transaction_id,
&inputs,
transaction_payload.unlocks(),
);

Ok(semantic_validation(
context,
inputs.as_slice(),
transaction_payload.unlocks(),
)?)
Ok(context.validate()?)
}

/// Verifies that the signed transaction payload doesn't exceed the block size limit with 8 parents.
Expand Down
11 changes: 5 additions & 6 deletions sdk/src/types/block/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub use self::{
restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress},
};
use crate::types::block::{
output::{Output, OutputId},
semantic::{TransactionFailureReason, ValidationContext},
output::Output,
semantic::{SemanticValidationContext, TransactionFailureReason},
signature::Signature,
unlock::Unlock,
ConvertTo, Error,
Expand Down Expand Up @@ -100,8 +100,7 @@ impl Address {
pub fn unlock(
&self,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
match (self, unlock) {
(Self::Ed25519(ed25519_address), Unlock::Signature(unlock)) => {
Expand All @@ -128,7 +127,7 @@ impl Address {
}
(Self::Account(account_address), Unlock::Account(unlock)) => {
// PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
if let (output_id, Output::Account(account_output)) = inputs[unlock.index() as usize] {
if let (output_id, Output::Account(account_output)) = context.inputs[unlock.index() as usize] {
if &account_output.account_id_non_null(output_id) != account_address.account_id() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
Expand All @@ -141,7 +140,7 @@ impl Address {
}
(Self::Nft(nft_address), Unlock::Nft(unlock)) => {
// PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
if let (output_id, Output::Nft(nft_output)) = inputs[unlock.index() as usize] {
if let (output_id, Output::Nft(nft_output)) = context.inputs[unlock.index() as usize] {
if &nft_output.nft_id_non_null(output_id) != nft_address.nft_id() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
Expand Down
19 changes: 9 additions & 10 deletions sdk/src/types/block/output/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::types::{
},
payload::signed_transaction::TransactionCapabilityFlag,
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
unlock::Unlock,
Error,
},
Expand Down Expand Up @@ -523,8 +523,7 @@ impl AccountOutput {
&self,
output_id: &OutputId,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
let account_id = if self.account_id().is_null() {
AccountId::from(output_id)
Expand All @@ -536,25 +535,25 @@ impl AccountOutput {
match next_state {
Some(Output::Account(next_state)) => {
if self.state_index() == next_state.state_index() {
self.governor_address().unlock(unlock, inputs, context)?;
self.governor_address().unlock(unlock, context)?;
} else {
self.state_controller_address().unlock(unlock, inputs, context)?;
self.state_controller_address().unlock(unlock, context)?;
// Only a state transition can be used to consider the account address for output unlocks and
// sender/issuer validations.
context
.unlocked_addresses
.insert(Address::from(AccountAddress::from(account_id)));
}
}
None => self.governor_address().unlock(unlock, inputs, context)?,
None => self.governor_address().unlock(unlock, context)?,
// The next state can only be an account output since it is identified by an account chain identifier.
Some(_) => unreachable!(),
};

Ok(())
}

// Transition, just without full ValidationContext
// Transition, just without full SemanticValidationContext
pub(crate) fn transition_inner(
current_state: &Self,
next_state: &Self,
Expand Down Expand Up @@ -622,7 +621,7 @@ impl AccountOutput {
}

impl StateTransitionVerifier for AccountOutput {
fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !next_state.account_id.is_null() {
return Err(StateTransitionError::NonZeroCreatedId);
}
Expand All @@ -639,7 +638,7 @@ impl StateTransitionVerifier for AccountOutput {
fn transition(
current_state: &Self,
next_state: &Self,
context: &ValidationContext<'_>,
context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
Self::transition_inner(
current_state,
Expand All @@ -649,7 +648,7 @@ impl StateTransitionVerifier for AccountOutput {
)
}

fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !context
.transaction
.has_capability(TransactionCapabilityFlag::DestroyAccountOutputs)
Expand Down
7 changes: 3 additions & 4 deletions sdk/src/types/block/output/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::types::{
NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure,
},
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
unlock::Unlock,
Error,
},
Expand Down Expand Up @@ -301,12 +301,11 @@ impl BasicOutput {
&self,
_output_id: &OutputId,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
self.unlock_conditions()
.locked_address(self.address(), context.transaction.creation_slot())
.unlock(unlock, inputs, context)
.unlock(unlock, context)
}

/// Returns the address of the unlock conditions if the output is a simple deposit.
Expand Down
18 changes: 10 additions & 8 deletions sdk/src/types/block/output/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::types::{
OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier,
},
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
slot::EpochIndex,
unlock::Unlock,
Error,
Expand Down Expand Up @@ -352,15 +352,14 @@ impl DelegationOutput {
&self,
_output_id: &OutputId,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
self.unlock_conditions()
.locked_address(self.address(), context.transaction.creation_slot())
.unlock(unlock, inputs, context)
.unlock(unlock, context)
}

// Transition, just without full ValidationContext.
// Transition, just without full SemanticValidationContext.
pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> {
#[allow(clippy::nonminimal_bool)]
if !(current_state.delegation_id.is_null() && !next_state.delegation_id().is_null()) {
Expand All @@ -379,7 +378,7 @@ impl DelegationOutput {
}

impl StateTransitionVerifier for DelegationOutput {
fn creation(next_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn creation(next_state: &Self, _context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !next_state.delegation_id.is_null() {
return Err(StateTransitionError::NonZeroCreatedId);
}
Expand All @@ -398,12 +397,15 @@ impl StateTransitionVerifier for DelegationOutput {
fn transition(
current_state: &Self,
next_state: &Self,
_context: &ValidationContext<'_>,
_context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
Self::transition_inner(current_state, next_state)
}

fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(
_current_state: &Self,
_context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
// TODO handle mana rewards
Ok(())
}
Expand Down
15 changes: 7 additions & 8 deletions sdk/src/types/block/output/foundry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::types::{
},
payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag},
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
unlock::Unlock,
Error,
},
Expand Down Expand Up @@ -449,13 +449,12 @@ impl FoundryOutput {
&self,
_output_id: &OutputId,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
Address::from(*self.account_address()).unlock(unlock, inputs, context)
Address::from(*self.account_address()).unlock(unlock, context)
}

// Transition, just without full ValidationContext
// Transition, just without full SemanticValidationContext
pub(crate) fn transition_inner(
current_state: &Self,
next_state: &Self,
Expand Down Expand Up @@ -544,7 +543,7 @@ impl FoundryOutput {
}

impl StateTransitionVerifier for FoundryOutput {
fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
let account_chain_id = ChainId::from(*next_state.account_address().account_id());

if let (Some(Output::Account(input_account)), Some(Output::Account(output_account))) = (
Expand Down Expand Up @@ -579,7 +578,7 @@ impl StateTransitionVerifier for FoundryOutput {
fn transition(
current_state: &Self,
next_state: &Self,
context: &ValidationContext<'_>,
context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
Self::transition_inner(
current_state,
Expand All @@ -590,7 +589,7 @@ impl StateTransitionVerifier for FoundryOutput {
)
}

fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !context
.transaction
.has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs)
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub use self::{
unlock_condition::{UnlockCondition, UnlockConditions},
};
use super::protocol::ProtocolParameters;
use crate::types::block::{address::Address, semantic::ValidationContext, slot::SlotIndex, Error};
use crate::types::block::{address::Address, semantic::SemanticValidationContext, slot::SlotIndex, Error};

/// The maximum number of outputs of a transaction.
pub const OUTPUT_COUNT_MAX: u16 = 128;
Expand Down Expand Up @@ -290,7 +290,7 @@ impl Output {
pub fn verify_state_transition(
current_state: Option<&Self>,
next_state: Option<&Self>,
context: &ValidationContext<'_>,
context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
match (current_state, next_state) {
// Creations.
Expand Down
15 changes: 7 additions & 8 deletions sdk/src/types/block/output/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::types::{
},
payload::signed_transaction::TransactionCapabilityFlag,
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
semantic::{SemanticValidationContext, TransactionFailureReason},
unlock::Unlock,
Error,
},
Expand Down Expand Up @@ -408,12 +408,11 @@ impl NftOutput {
&self,
output_id: &OutputId,
unlock: &Unlock,
inputs: &[(&OutputId, &Output)],
context: &mut ValidationContext<'_>,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
self.unlock_conditions()
.locked_address(self.address(), context.transaction.creation_slot())
.unlock(unlock, inputs, context)?;
.unlock(unlock, context)?;

let nft_id = if self.nft_id().is_null() {
NftId::from(output_id)
Expand All @@ -428,7 +427,7 @@ impl NftOutput {
Ok(())
}

// Transition, just without full ValidationContext
// Transition, just without full SemanticValidationContext
pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> {
if current_state.immutable_features != next_state.immutable_features {
return Err(StateTransitionError::MutatedImmutableField);
Expand All @@ -438,7 +437,7 @@ impl NftOutput {
}

impl StateTransitionVerifier for NftOutput {
fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !next_state.nft_id.is_null() {
return Err(StateTransitionError::NonZeroCreatedId);
}
Expand All @@ -455,12 +454,12 @@ impl StateTransitionVerifier for NftOutput {
fn transition(
current_state: &Self,
next_state: &Self,
_context: &ValidationContext<'_>,
_context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError> {
Self::transition_inner(current_state, next_state)
}

fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(_current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> {
if !context
.transaction
.has_capability(TransactionCapabilityFlag::DestroyNftOutputs)
Expand Down
8 changes: 4 additions & 4 deletions sdk/src/types/block/output/state_transition.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::types::block::semantic::ValidationContext;
use crate::types::block::semantic::SemanticValidationContext;

///
#[allow(missing_docs)]
Expand Down Expand Up @@ -33,15 +33,15 @@ pub enum StateTransitionError {
///
pub trait StateTransitionVerifier {
///
fn creation(next_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError>;
fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>;

///
fn transition(
current_state: &Self,
next_state: &Self,
context: &ValidationContext<'_>,
context: &SemanticValidationContext<'_>,
) -> Result<(), StateTransitionError>;

///
fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError>;
fn destruction(current_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError>;
}
Loading

0 comments on commit cbf7b9f

Please sign in to comment.