Skip to content

Commit

Permalink
Move unlocks to semantic module (#1772)
Browse files Browse the repository at this point in the history
* Semantic module

* Move transitions to semantic

* Error module

* SemanticValidationContext::address_unlock

* Remove some from

* Use id_non_null

* SemanticValidationContext::output_unlock
  • Loading branch information
thibault-martinez authored Dec 13, 2023
1 parent 51011bd commit 8951f4c
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 424 deletions.
92 changes: 1 addition & 91 deletions sdk/src/types/block/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ pub use self::{
};
use crate::{
types::block::{
output::{Output, StorageScore, StorageScoreParameters},
semantic::{SemanticValidationContext, TransactionFailureReason},
signature::Signature,
unlock::Unlock,
output::{StorageScore, StorageScoreParameters},
Error,
},
utils::ConvertTo,
Expand Down Expand Up @@ -126,93 +123,6 @@ impl Address {
pub fn is_valid_bech32(address: &str) -> bool {
Self::try_from_bech32(address).is_ok()
}

///
pub fn unlock(
&self,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
match (self, unlock) {
(Self::Ed25519(ed25519_address), Unlock::Signature(unlock)) => {
if context.unlocked_addresses.contains(self) {
return Err(TransactionFailureReason::InvalidInputUnlock);
}

let Signature::Ed25519(signature) = unlock.signature();

if signature
.is_valid(context.transaction_signing_hash.as_ref(), ed25519_address)
.is_err()
{
return Err(TransactionFailureReason::InvalidUnlockBlockSignature);
}

context.unlocked_addresses.insert(self.clone());
}
(Self::Ed25519(_), Unlock::Reference(_)) => {
// TODO actually check that it was unlocked by the same signature.
if !context.unlocked_addresses.contains(self) {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
}
(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)) = context.inputs[unlock.index() as usize] {
if &account_output.account_id_non_null(output_id) != account_address.account_id() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
if !context.unlocked_addresses.contains(self) {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
} else {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
}
(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)) = context.inputs[unlock.index() as usize] {
if &nft_output.nft_id_non_null(output_id) != nft_address.nft_id() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
if !context.unlocked_addresses.contains(self) {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
} else {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
}
// TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason.
(Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed),
(Self::ImplicitAccountCreation(implicit_account_creation_address), _) => {
return Self::from(*implicit_account_creation_address.ed25519_address()).unlock(unlock, context);
}
(Self::Multi(multi_address), Unlock::Multi(unlock)) => {
if multi_address.len() != unlock.len() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}

let mut cumulative_unlocked_weight = 0u16;

for (address, unlock) in multi_address.addresses().iter().zip(unlock.unlocks()) {
if !unlock.is_empty() {
address.unlock(unlock, context)?;
cumulative_unlocked_weight += address.weight() as u16;
}
}

if cumulative_unlocked_weight < multi_address.threshold() {
return Err(TransactionFailureReason::InvalidInputUnlock);
}
}
(Self::Restricted(restricted_address), _) => {
return restricted_address.address().unlock(unlock, context);
}
_ => return Err(TransactionFailureReason::InvalidInputUnlock),
}

Ok(())
}
}

impl StorageScore for Address {
Expand Down
34 changes: 1 addition & 33 deletions sdk/src/types/block/output/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use crate::types::block::{
ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason},
unlock::Unlock,
semantic::StateTransitionError,
Error,
};

Expand Down Expand Up @@ -375,37 +374,6 @@ impl AccountOutput {
AccountAddress::new(self.account_id_non_null(output_id))
}

///
pub fn unlock(
&self,
output_id: &OutputId,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
self.unlock_conditions()
.locked_address(
self.address(),
None,
context.protocol_parameters.committable_age_range(),
)
// Safe to unwrap, AccountOutput can't have an expiration unlock condition.
.unwrap()
.unwrap()
.unlock(unlock, context)?;

let account_id = if self.account_id().is_null() {
AccountId::from(output_id)
} else {
*self.account_id()
};

context
.unlocked_addresses
.insert(Address::from(AccountAddress::from(account_id)));

Ok(())
}

// Transition, just without full SemanticValidationContext
pub(crate) fn transition_inner(
current_state: &Self,
Expand Down
16 changes: 5 additions & 11 deletions sdk/src/types/block/output/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,27 +427,21 @@ impl AnchorOutput {
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
let anchor_id = if self.anchor_id().is_null() {
AnchorId::from(output_id)
} else {
*self.anchor_id()
};
let anchor_id = self.anchor_id_non_null(output_id);
let next_state = context.output_chains.get(&ChainId::from(anchor_id));

match next_state {
Some(Output::Anchor(next_state)) => {
if self.state_index() == next_state.state_index() {
self.governor_address().unlock(unlock, context)?;
context.address_unlock(self.governor_address(), unlock)?;
} else {
self.state_controller_address().unlock(unlock, context)?;
context.address_unlock(self.state_controller_address(), unlock)?;
// Only a state transition can be used to consider the anchor address for output unlocks and
// sender/issuer validations.
context
.unlocked_addresses
.insert(Address::from(AnchorAddress::from(anchor_id)));
context.unlocked_addresses.insert(Address::from(anchor_id));
}
}
None => self.governor_address().unlock(unlock, context)?,
None => context.address_unlock(self.governor_address(), unlock)?,
// The next state can only be an anchor output since it is identified by an anchor chain identifier.
Some(_) => unreachable!(),
};
Expand Down
29 changes: 1 addition & 28 deletions sdk/src/types/block/output/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ use crate::types::block::{
verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition,
UnlockCondition, UnlockConditionFlags, UnlockConditions,
},
MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::{SemanticValidationContext, TransactionFailureReason},
unlock::Unlock,
Error,
};

Expand Down Expand Up @@ -311,31 +309,6 @@ impl BasicOutput {
.unwrap()
}

///
pub fn unlock(
&self,
_output_id: &OutputId,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
let slot_index = context
.transaction
.context_inputs()
.iter()
.find_map(|c| c.as_commitment_opt().map(|c| c.slot_index()));

self.unlock_conditions()
.locked_address(
self.address(),
slot_index,
context.protocol_parameters.committable_age_range(),
)
.map_err(|_| TransactionFailureReason::InvalidCommitmentContextInput)?
// because of expiration the input can't be unlocked at this time
.ok_or(TransactionFailureReason::SemanticValidationFailed)?
.unlock(unlock, context)
}

/// Returns the address of the unlock conditions if the output is a simple deposit.
/// Simple deposit outputs are basic outputs with only an address unlock condition, no native tokens and no
/// features. They are used to return storage deposits.
Expand Down
22 changes: 1 addition & 21 deletions sdk/src/types/block/output/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ use crate::types::block::{
MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason},
semantic::StateTransitionError,
slot::EpochIndex,
unlock::Unlock,
Error,
};

Expand Down Expand Up @@ -315,25 +314,6 @@ impl DelegationOutput {
ChainId::Delegation(self.delegation_id)
}

/// Tries to unlock the [`DelegationOutput`].
pub fn unlock(
&self,
_output_id: &OutputId,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
self.unlock_conditions()
.locked_address(
self.address(),
None,
context.protocol_parameters.committable_age_range(),
)
// Safe to unwrap, DelegationOutput can't have an expiration unlock condition.
.unwrap()
.unwrap()
.unlock(unlock, context)
}

// Transition, just without full SemanticValidationContext.
pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> {
#[allow(clippy::nonminimal_bool)]
Expand Down
17 changes: 3 additions & 14 deletions sdk/src/types/block/output/foundry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ use crate::types::block::{
account::AccountId,
feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature},
unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StorageScore,
StorageScoreParameters, TokenId, TokenScheme,
ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters,
TokenId, TokenScheme,
},
payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason},
unlock::Unlock,
semantic::{StateTransitionError, TransactionFailureReason},
Error,
};

Expand Down Expand Up @@ -402,16 +401,6 @@ impl FoundryOutput {
ChainId::Foundry(self.id())
}

///
pub fn unlock(
&self,
_output_id: &OutputId,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
Address::from(*self.account_address()).unlock(unlock, context)
}

// Transition, just without full SemanticValidationContext
pub(crate) fn transition_inner(
current_state: &Self,
Expand Down
40 changes: 1 addition & 39 deletions sdk/src/types/block/output/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ use crate::types::block::{
StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::{SemanticValidationContext, StateTransitionError, TransactionFailureReason},
unlock::Unlock,
semantic::StateTransitionError,
Error,
};

Expand Down Expand Up @@ -406,43 +405,6 @@ impl NftOutput {
NftAddress::new(self.nft_id_non_null(output_id))
}

///
pub fn unlock(
&self,
output_id: &OutputId,
unlock: &Unlock,
context: &mut SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
let slot_index = context
.transaction
.context_inputs()
.iter()
.find_map(|c| c.as_commitment_opt().map(|c| c.slot_index()));

self.unlock_conditions()
.locked_address(
self.address(),
slot_index,
context.protocol_parameters.committable_age_range(),
)
.map_err(|_| TransactionFailureReason::InvalidCommitmentContextInput)?
// because of expiration the input can't be unlocked at this time
.ok_or(TransactionFailureReason::SemanticValidationFailed)?
.unlock(unlock, context)?;

let nft_id = if self.nft_id().is_null() {
NftId::from(output_id)
} else {
*self.nft_id()
};

context
.unlocked_addresses
.insert(Address::from(NftAddress::from(nft_id)));

Ok(())
}

// 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 {
Expand Down
Loading

0 comments on commit 8951f4c

Please sign in to comment.