Skip to content
6 changes: 6 additions & 0 deletions ledger/block/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ workspace = true
[dependencies.snarkvm-ledger-puzzle]
workspace = true

[dependencies.snarkvm-synthesizer-error]
workspace = true

[dependencies.snarkvm-synthesizer-program]
workspace = true

Expand All @@ -80,6 +83,9 @@ features = [ "serde" ]
[dependencies.rayon]
workspace = true

[dependencies.serde]
workspace = true

[dependencies.serde_json]
workspace = true
features = [ "preserve_order" ]
Expand Down
43 changes: 43 additions & 0 deletions ledger/block/src/transaction/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
// limitations under the License.

use super::*;
use crate::RejectionReason;

use snarkvm_synthesizer_error::ProcessFinalizeError;

impl<N: Network> FromBytes for Transaction<N> {
/// Reads the transaction from the buffer.
Expand Down Expand Up @@ -139,6 +142,46 @@ impl<N: Network> ToBytes for Transaction<N> {
}
}

impl FromBytes for RejectionReason {
/// Reads the transaction from the buffer.
#[inline]
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
// Read the variant.
let variant = u8::read_le(&mut reader)?;

let reason = match variant {
0 => Self::AlreadyDeployedInTheBlock,
1 => {
let finalize_error = ProcessFinalizeError::read_le(&mut reader)?;
Self::FailedToFinalize(finalize_error)
}
_ => {
return Err(error("Invalid RejectionReason variant"));
}
};

Ok(reason)
}
}

impl ToBytes for RejectionReason {
/// Writes the transaction to the buffer.
#[inline]
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
match self {
Self::AlreadyDeployedInTheBlock => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a more symmetric FailedToDeploy(reason)

// Write the variant.
0u8.write_le(&mut writer)
}
Self::FailedToFinalize(finalize_error) => {
// Write the variant.
1u8.write_le(&mut writer)?;
finalize_error.write_le(&mut writer)
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
16 changes: 13 additions & 3 deletions ledger/block/src/transactions/confirmed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ pub mod test_helpers {
#[cfg(test)]
mod test {
use super::*;
use crate::transactions::confirmed::test_helpers;
use crate::{RejectionReason, transactions::confirmed::test_helpers};

type CurrentNetwork = console::network::MainnetV0;

Expand Down Expand Up @@ -643,6 +643,7 @@ mod test {
let rejected = Rejected::new_deployment(
*deployment_transaction.owner().unwrap(),
deployment_transaction.deployment().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap();
let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand All @@ -654,6 +655,7 @@ mod test {
let rejected = Rejected::new_deployment(
*deployment_transaction.owner().unwrap(),
deployment_transaction.deployment().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap();
let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand All @@ -665,6 +667,7 @@ mod test {
let rejected = Rejected::new_deployment(
*deployment_transaction.owner().unwrap(),
deployment_transaction.deployment().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap();
let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand All @@ -676,6 +679,7 @@ mod test {
let rejected = Rejected::new_deployment(
*deployment_transaction.owner().unwrap(),
deployment_transaction.deployment().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap();
let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand All @@ -685,7 +689,10 @@ mod test {
// Ensure that the unconfirmed transaction of a rejected execute is not equivalent to its confirmed transaction.
let execution_transaction =
crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
let rejected = Rejected::new_execution(execution_transaction.execution().unwrap().clone());
let rejected = Rejected::new_execution(
execution_transaction.execution().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(execution_transaction.fee_transition().unwrap()).unwrap();
let rejected_execute =
ConfirmedTransaction::rejected_execute(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand All @@ -694,7 +701,10 @@ mod test {

let execution_transaction =
crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0);
let rejected = Rejected::new_execution(execution_transaction.execution().unwrap().clone());
let rejected = Rejected::new_execution(
execution_transaction.execution().unwrap().clone(),
RejectionReason::FailedToFinalize,
);
let fee = Transaction::from_fee(execution_transaction.fee_transition().unwrap()).unwrap();
let rejected_execute =
ConfirmedTransaction::rejected_execute(Uniform::rand(rng), fee, rejected, vec![]).unwrap();
Expand Down
20 changes: 14 additions & 6 deletions ledger/block/src/transactions/rejected/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ impl<N: Network> FromBytes for Rejected<N> {
let program_owner = ProgramOwner::read_le(&mut reader)?;
// Read the deployment.
let deployment = Deployment::read_le(&mut reader)?;
// Read the rejection reason.
let rejection_reason = RejectionReason::read_le(&mut reader)?;
// Return the rejected deployment.
Ok(Self::new_deployment(program_owner, deployment))
Ok(Self::new_deployment(program_owner, deployment, rejection_reason))
}
1 => {
// Read the execution.
let execution = Execution::read_le(&mut reader)?;
// Read the rejection reason.
let rejection_reason = RejectionReason::read_le(&mut reader)?;
// Return the rejected execution.
Ok(Self::new_execution(execution))
Ok(Self::new_execution(execution, rejection_reason))
}
2.. => Err(error(format!("Failed to decode rejected transaction variant {variant}"))),
}
Expand All @@ -43,19 +47,23 @@ impl<N: Network> ToBytes for Rejected<N> {
/// Writes the rejected transaction to a buffer.
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
match self {
Self::Deployment(program_owner, deployment) => {
Self::Deployment(program_owner, deployment, rejection_reason) => {
// Write the variant.
0u8.write_le(&mut writer)?;
// Write the program owner.
program_owner.write_le(&mut writer)?;
// Write the deployment.
deployment.write_le(&mut writer)
deployment.write_le(&mut writer)?;
// Write the rejection reason.
rejection_reason.write_le(&mut writer)
}
Self::Execution(execution) => {
Self::Execution(execution, rejection_reason) => {
// Write the variant.
1u8.write_le(&mut writer)?;
// Write the execution.
execution.write_le(&mut writer)
execution.write_le(&mut writer)?;
// Write the rejection reason.
rejection_reason.write_le(&mut writer)
}
}
}
Expand Down
49 changes: 31 additions & 18 deletions ledger/block/src/transactions/rejected/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,31 @@ mod serialize;
mod string;

use super::*;
use snarkvm_synthesizer_error::ProcessFinalizeError;

use crate::{Deployment, Execution, Fee};
use serde::{Deserialize, Serialize};

/// A wrapper around the rejected deployment or execution.
#[derive(Clone, PartialEq, Eq)]
pub enum Rejected<N: Network> {
Deployment(ProgramOwner<N>, Box<Deployment<N>>),
Execution(Box<Execution<N>>),
Deployment(ProgramOwner<N>, Box<Deployment<N>>, RejectionReason),
Execution(Box<Execution<N>>, RejectionReason),
}

impl<N: Network> Rejected<N> {
/// Initializes a rejected deployment.
pub fn new_deployment(program_owner: ProgramOwner<N>, deployment: Deployment<N>) -> Self {
Self::Deployment(program_owner, Box::new(deployment))
pub fn new_deployment(
program_owner: ProgramOwner<N>,
deployment: Deployment<N>,
rejection_reason: RejectionReason,
) -> Self {
Self::Deployment(program_owner, Box::new(deployment), rejection_reason)
}

/// Initializes a rejected execution.
pub fn new_execution(execution: Execution<N>) -> Self {
Self::Execution(Box::new(execution))
pub fn new_execution(execution: Execution<N>, rejection_reason: RejectionReason) -> Self {
Self::Execution(Box::new(execution), rejection_reason)
}

/// Returns true if the rejected transaction is a deployment.
Expand All @@ -52,32 +58,32 @@ impl<N: Network> Rejected<N> {
/// Returns the program owner of the rejected deployment.
pub fn program_owner(&self) -> Option<&ProgramOwner<N>> {
match self {
Self::Deployment(program_owner, _) => Some(program_owner),
Self::Execution(_) => None,
Self::Deployment(program_owner, ..) => Some(program_owner),
Self::Execution(..) => None,
}
}

/// Returns the rejected deployment.
pub fn deployment(&self) -> Option<&Deployment<N>> {
match self {
Self::Deployment(_, deployment) => Some(deployment),
Self::Execution(_) => None,
Self::Deployment(_, deployment, ..) => Some(deployment),
Self::Execution(..) => None,
}
}

/// Returns the rejected execution.
pub fn execution(&self) -> Option<&Execution<N>> {
match self {
Self::Deployment(_, _) => None,
Self::Execution(execution) => Some(execution),
Self::Deployment(..) => None,
Self::Execution(execution, ..) => Some(execution),
}
}

/// Returns the rejected ID.
pub fn to_id(&self) -> Result<Field<N>> {
match self {
Self::Deployment(_, deployment) => deployment.to_deployment_id(),
Self::Execution(execution) => execution.to_execution_id(),
Self::Deployment(_, deployment, ..) => deployment.to_deployment_id(),
Self::Execution(execution, ..) => execution.to_execution_id(),
}
}

Expand All @@ -87,14 +93,21 @@ impl<N: Network> Rejected<N> {
pub fn to_unconfirmed_id(&self, fee: &Option<Fee<N>>) -> Result<Field<N>> {
// Compute the deployment or execution tree.
let tree = match self {
Self::Deployment(_, deployment) => Transaction::deployment_tree(deployment)?,
Self::Execution(execution) => Transaction::execution_tree(execution)?,
Self::Deployment(_, deployment, ..) => Transaction::deployment_tree(deployment)?,
Self::Execution(execution, ..) => Transaction::execution_tree(execution)?,
};
// Construct the transaction tree and return the unconfirmed transaction ID.
Ok(*Transaction::transaction_tree(tree, fee.as_ref())?.root())
}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[non_exhaustive]
pub enum RejectionReason {
AlreadyDeployedInTheBlock,
FailedToFinalize(ProcessFinalizeError),
}

#[cfg(test)]
pub mod test_helpers {
use super::*;
Expand Down Expand Up @@ -126,7 +139,7 @@ pub mod test_helpers {
let program_owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap();

// Return the rejected deployment.
Rejected::new_deployment(program_owner, deployment)
Rejected::new_deployment(program_owner, deployment, RejectionReason::FailedToFinalize)
}

/// Samples a rejected execution.
Expand All @@ -139,7 +152,7 @@ pub mod test_helpers {
};

// Return the rejected execution.
Rejected::new_execution(*execution)
Rejected::new_execution(*execution, RejectionReason::FailedToFinalize)
}

/// Sample a list of randomly rejected transactions.
Expand Down
20 changes: 14 additions & 6 deletions ledger/block/src/transactions/rejected/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ impl<N: Network> Serialize for Rejected<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match serializer.is_human_readable() {
true => match self {
Self::Deployment(program_owner, deployment) => {
let mut object = serializer.serialize_struct("Rejected", 3)?;
Self::Deployment(program_owner, deployment, rejection_reason) => {
let mut object = serializer.serialize_struct("Rejected", 4)?;
object.serialize_field("type", "deployment")?;
object.serialize_field("program_owner", program_owner)?;
object.serialize_field("deployment", deployment)?;
object.serialize_field("rejection_reason", rejection_reason)?;
object.end()
}
Self::Execution(execution) => {
let mut object = serializer.serialize_struct("Rejected", 2)?;
Self::Execution(execution, rejection_reason) => {
let mut object = serializer.serialize_struct("Rejected", 3)?;
object.serialize_field("type", "execution")?;
object.serialize_field("execution", execution)?;
object.serialize_field("rejection_reason", rejection_reason)?;
object.end()
}
},
Expand Down Expand Up @@ -59,14 +61,20 @@ impl<'de, N: Network> Deserialize<'de> for Rejected<N> {
// Parse the deployment.
let deployment: Deployment<N> =
DeserializeExt::take_from_value::<D>(&mut object, "deployment")?;
// Parse the rejection reason.
let rejection_reason: RejectionReason =
DeserializeExt::take_from_value::<D>(&mut object, "rejection_reason")?;
// Return the rejected deployment.
Ok(Self::new_deployment(program_owner, deployment))
Ok(Self::new_deployment(program_owner, deployment, rejection_reason))
}
Some("execution") => {
// Parse the execution.
let execution: Execution<N> = DeserializeExt::take_from_value::<D>(&mut object, "execution")?;
// Parse the rejection reason.
let rejection_reason: RejectionReason =
DeserializeExt::take_from_value::<D>(&mut object, "rejection_reason")?;
// Return the rejected execution.
Ok(Self::new_execution(execution))
Ok(Self::new_execution(execution, rejection_reason))
}
_ => Err(de::Error::custom("Invalid rejected transaction type")),
}
Expand Down
10 changes: 7 additions & 3 deletions ledger/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use console::{
types::U16,
};
use snarkvm_ledger_authority::Authority;
use snarkvm_ledger_block::{Block, ConfirmedTransaction, Execution, Ratify, Rejected, Transaction};
use snarkvm_ledger_block::{Block, ConfirmedTransaction, Execution, Ratify, Rejected, RejectionReason, Transaction};
use snarkvm_ledger_committee::{Committee, MIN_VALIDATOR_STAKE};
use snarkvm_ledger_narwhal::{BatchHeader, Data, Subdag, Transmission, TransmissionID};
use snarkvm_ledger_store::ConsensusStore;
Expand Down Expand Up @@ -553,8 +553,12 @@ finalize failed_assert:
assert!(confirmed_transaction.is_rejected());
if let Transaction::Execute(_, _, execution, fee) = failed_assert_transaction {
let fee_transaction = Transaction::from_fee(fee.unwrap()).unwrap();
let expected_confirmed_transaction =
ConfirmedTransaction::RejectedExecute(0, fee_transaction, Rejected::new_execution(*execution), vec![]);
let expected_confirmed_transaction = ConfirmedTransaction::RejectedExecute(
0,
fee_transaction,
Rejected::new_execution(*execution, RejectionReason::FailedToFinalize),
vec![],
);

assert_eq!(confirmed_transaction, &expected_confirmed_transaction);
}
Expand Down
Loading