diff --git a/napi-pallas/Cargo.toml b/napi-pallas/Cargo.toml index cf2d72f..8b53243 100644 --- a/napi-pallas/Cargo.toml +++ b/napi-pallas/Cargo.toml @@ -12,7 +12,7 @@ hex = "0.4.3" # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix napi = { version = "2.12.2", default-features = false, features = ["napi4"] } napi-derive = "2.12.2" -pallas = { git = "https://github.com/alegadea/pallas.git", rev = "54ffc77" , features = ["unstable"]} +pallas = { git = "https://github.com/txpipe/pallas.git", rev = "2f97cbe" , features = ["unstable"]} [build-dependencies] napi-build = "2.0.1" diff --git a/napi-pallas/index.d.ts b/napi-pallas/index.d.ts index 7254742..21e5cf8 100644 --- a/napi-pallas/index.d.ts +++ b/napi-pallas/index.d.ts @@ -46,4 +46,5 @@ export interface Validation { } export interface Validations { validations: Array + era: string } diff --git a/napi-pallas/src/lib.rs b/napi-pallas/src/lib.rs index af1cc3a..5f175ff 100644 --- a/napi-pallas/src/lib.rs +++ b/napi-pallas/src/lib.rs @@ -212,6 +212,7 @@ impl Validation { #[napi(object)] pub struct Validations { pub validations: Vec, + pub era: String, } impl Validations { @@ -224,4 +225,11 @@ impl Validations { self } + + pub fn with_era(self, era: impl ToString) -> Self { + Self { + era: era.to_string(), + ..self + } + } } diff --git a/napi-pallas/src/validations/alonzo.rs b/napi-pallas/src/validations/alonzo.rs index 8393406..dace7cf 100644 --- a/napi-pallas/src/validations/alonzo.rs +++ b/napi-pallas/src/validations/alonzo.rs @@ -3,6 +3,6 @@ use pallas::ledger::primitives::alonzo::MintedTx; use crate::Validations; pub fn validate_alonzo(mtx_a: &MintedTx) -> Validations { - let out = Validations::new(); + let out = Validations::new().with_era("Alonzo".to_string()); out } diff --git a/napi-pallas/src/validations/babbage.rs b/napi-pallas/src/validations/babbage.rs index 9006454..cf44dae 100644 --- a/napi-pallas/src/validations/babbage.rs +++ b/napi-pallas/src/validations/babbage.rs @@ -1,22 +1,325 @@ use crate::{Validation, Validations}; use pallas::{ - applying::babbage::check_ins_not_empty, + applying::{ + babbage::{ + check_all_ins_in_utxos, check_auxiliary_data, check_fee, check_ins_not_empty, + check_languages, check_min_lovelace, check_minting, check_network_id, check_output_val_size, + check_preservation_of_value, check_script_data_hash, check_tx_ex_units, check_tx_size, + check_tx_validity_interval, check_well_formedness, check_witness_set, + }, + utils::{get_babbage_tx_size, BabbageProtParams, FeePolicy}, + Environment, MultiEraProtParams, UTxOs, + }, ledger::primitives::babbage::{MintedTransactionBody, MintedTx as BabbageMintedTx}, }; use super::validate::set_description; +// &The following validations only require the tx fn validate_babbage_ins_not_empty(mtx: &BabbageMintedTx) -> Validation { let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); let res = check_ins_not_empty(tx_body); - let description = set_description(&res, "Inputs are not empty".to_string()); + let description = set_description( + &res, + "The set of transaction inputs is not empty.".to_string(), + ); return Validation::new() .with_name("Non empty inputs".to_string()) .with_value(res.is_ok()) .with_description(description); } +fn validate_babbage_minting(mtx: &BabbageMintedTx) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_minting(tx_body, mtx); + let description = set_description( + &res, + "Each minted / burned asset is paired with an appropriate native script or Plutus script" + .to_string(), + ); + return Validation::new() + .with_name("Minting policy".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_well_formed(mtx: &BabbageMintedTx) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_well_formedness(tx_body, mtx); + let description = set_description(&res, "The transaction is well-formed".to_string()); + return Validation::new() + .with_name("Well formedness".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_auxiliary_data(mtx: &BabbageMintedTx) -> Validation { + let tx_body = &mtx.transaction_body.clone(); + let res = check_auxiliary_data(tx_body, mtx); + let description = set_description( + &res, + "The metadata of the transaction is valid.".to_string(), + ); + return Validation::new() + .with_name("Auxiliary data".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// &The following validations also require the protocol parameters +fn validate_babbage_min_lovelace( + mtx: &BabbageMintedTx, + prot_pps: &BabbageProtParams, +) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + + let res = check_min_lovelace(tx_body, prot_pps); + let description = set_description( + &res, + "All transaction outputs (regular outputs and collateral outputs) contains at least the minimum lovelace.".to_string(), + ); + return Validation::new() + .with_name("Minimum lovelace".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_output_val_size( + mtx: &BabbageMintedTx, + prot_pps: &BabbageProtParams, +) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_output_val_size(tx_body, prot_pps); + let description = set_description( + &res, + "The size of the value in each of the outputs is not greater than the maximum allowed." + .to_string(), + ); + return Validation::new() + .with_name("Output value size".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_tx_ex_units(mtx: &BabbageMintedTx, prot_pps: &BabbageProtParams) -> Validation { + let res = check_tx_ex_units(mtx, prot_pps); + let description = set_description( + &res, + "The number of execution units of the transaction does not exceed the maximum allowed." + .to_string(), + ); + return Validation::new() + .with_name("Transaction execution units".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// &The following validation also requires the tx size +fn validate_babbage_tx_size(size: &Option, prot_pps: &BabbageProtParams) -> Validation { + match size { + Some(size_value) => { + let res = check_tx_size(size_value, prot_pps); + let description = set_description( + &res, + "The size of the transaction does not exceed the maximum allowed.".to_string(), + ); + Validation::new() + .with_name("Transaction size".to_string()) + .with_value(res.is_ok()) + .with_description(description) + } + None => { + // Handle the case where size is None + // For example, return a specific validation result indicating that the size is not provided + Validation::new() + .with_name("Transaction size".to_string()) + .with_value(false) + .with_description("The transaction size could not be obtained.".to_string()) + } + } +} + +// &The following validation also requires the tx utxos +fn validate_babbage_fee( + mtx: &BabbageMintedTx, + size: &Option, + utxos: &UTxOs, + prot_pps: &BabbageProtParams, +) -> Validation { + match size { + Some(size_value) => { + let tx_body = &mtx.transaction_body.clone(); + let res = check_fee(tx_body, size_value, mtx, utxos, prot_pps); + let description = set_description(&res, "The fee of the transaction is valid.".to_string()); + return Validation::new() + .with_name("Fee".to_string()) + .with_value(res.is_ok()) + .with_description(description); + } + None => { + // Handle the case where size is None + // For example, return a specific validation result indicating that the size is not provided + Validation::new() + .with_name("Fee".to_string()) + .with_value(false) + .with_description("The transaction size could not be obtained.".to_string()) + } + } +} + +// &The following validations require the transaction and its utxos +fn validate_babbage_witness_set(mtx: &BabbageMintedTx, utxos: &UTxOs) -> Validation { + let res = check_witness_set(mtx, utxos); + let description = set_description( + &res, + "The witness set of the transaction is valid.".to_string(), + ); + return Validation::new() + .with_name("Witness set".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_all_ins_in_utxos(mtx: &BabbageMintedTx, utxos: &UTxOs) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_all_ins_in_utxos(tx_body, utxos); + let description = set_description( + &res, + "All transaction inputs, collateral inputs and reference inputs are in the UTxO".to_string(), + ); + return Validation::new() + .with_name("All inputs in UTxOs".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_preservation_of_value(mtx: &BabbageMintedTx, utxos: &UTxOs) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_preservation_of_value(tx_body, utxos); + let description = set_description( + &res, + "The preservation of value property holds.".to_string(), + ); + return Validation::new() + .with_name("Preservation of value".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// &The following validation also require the network and the block slot +fn validate_babbage_languages( + mtx: &BabbageMintedTx, + utxos: &UTxOs, + network_magic: &u32, + network_id: &u8, + block_slot: u64, +) -> Validation { + let res = check_languages(mtx, utxos, &network_magic, &network_id, &block_slot); + let description = set_description( + &res, + "The Plutus scripts and native scripts of the transaction are valid.".to_string(), + ); + return Validation::new() + .with_name("Languages".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_babbage_script_data_hash( + mtx: &BabbageMintedTx, + utxos: &UTxOs, + network_magic: &u32, + network_id: &u8, + block_slot: u64, +) -> Validation { + let tx_body = &mtx.transaction_body.clone(); + let res = check_script_data_hash( + tx_body, + mtx, + utxos, + &network_magic, + &network_id, + &block_slot, + ); + let description = set_description( + &res, + "The Plutus scripts and native scripts of the transaction are valid.".to_string(), + ); + return Validation::new() + .with_name("Languages".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// &The following validation requires the tx and the block slot +fn validate_babbage_tx_validity_interval(mtx: &BabbageMintedTx, block_slot: u64) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_tx_validity_interval(tx_body, &block_slot); + let description = set_description( + &res, + "The block slot is contained in the transaction validity interval.".to_string(), + ); + return Validation::new() + .with_name("Validity interval".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// &The following validation requires the tx and its network id +fn validate_babbage_network_id(mtx: &BabbageMintedTx, network_id: u8) -> Validation { + let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone(); + let res = check_network_id(tx_body, &network_id); + let description = set_description( + &res, + "The network ID of each regular output as well as that of the collateral output match the global network ID." + .to_string(), + ); + return Validation::new() + .with_name("Network id".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + pub fn validate_babbage(mtx_b: &BabbageMintedTx) -> Validations { - let out = Validations::new().add_new_validation(validate_babbage_ins_not_empty(&mtx_b)); + let tx_body: &MintedTransactionBody = &mtx_b.transaction_body.clone(); + let size: &Option = &get_babbage_tx_size(tx_body); + let prot_params = BabbageProtParams { + fee_policy: FeePolicy { + summand: 155381, + multiplier: 44, + }, + max_tx_size: 16384, + max_block_ex_mem: 62000000, + max_block_ex_steps: 20000000000, + max_tx_ex_mem: 14000000, + max_tx_ex_steps: 10000000000, + max_val_size: 5000, + collateral_percent: 150, + max_collateral_inputs: 3, + coins_per_utxo_word: 4310, + }; + + let env: Environment = Environment { + prot_params: MultiEraProtParams::Babbage(prot_params.clone()), + prot_magic: 764824073, // Mainnet. For preprod: 1. For preview: 2 + block_slot: 72316896, // TODO: Must be an input + network_id: 1, // Mainnet. For preprod: 0. For preview: 0 + }; + + let out = Validations::new() + .with_era("Babbage".to_string()) + .add_new_validation(validate_babbage_ins_not_empty(&mtx_b)) + .add_new_validation(validate_babbage_minting(&mtx_b)) + .add_new_validation(validate_babbage_well_formed(&mtx_b)) + .add_new_validation(validate_babbage_auxiliary_data(&mtx_b)) + .add_new_validation(validate_babbage_min_lovelace(&mtx_b, &prot_params)) + .add_new_validation(validate_babbage_output_val_size(&mtx_b, &prot_params)) + .add_new_validation(validate_babbage_tx_ex_units(&mtx_b, &prot_params)) + .add_new_validation(validate_babbage_tx_size(&size, &prot_params)) + .add_new_validation(validate_babbage_tx_validity_interval( + &mtx_b, + env.block_slot, + )) + .add_new_validation(validate_babbage_network_id(&mtx_b, env.network_id)); out } diff --git a/napi-pallas/src/validations/byron.rs b/napi-pallas/src/validations/byron.rs index 78cd641..1af97d9 100644 --- a/napi-pallas/src/validations/byron.rs +++ b/napi-pallas/src/validations/byron.rs @@ -1,6 +1,141 @@ -use crate::Validations; -use pallas::ledger::primitives::byron::MintedTxPayload; +use crate::{Validation, Validations}; +use pallas::{ + applying::{ + byron::{ + check_fees, check_ins_in_utxos, check_ins_not_empty, check_outs_have_lovelace, + check_outs_not_empty, check_size, check_witnesses, + }, + utils::{ByronProtParams, FeePolicy}, + UTxOs, + }, + codec::minicbor::encode, + ledger::primitives::byron::{MintedTxPayload, Tx}, +}; + +use super::validate::set_description; + +fn get_tx_size(tx: &Tx) -> u64 { + let mut buff: Vec = Vec::new(); + if encode(tx, &mut buff).is_ok() { + return buff.len() as u64; + } else { + return 0; + } +} + +// & The following validation requires the size and the protocol parameters +fn validate_byron_size(size: &u64, prot_pps: &ByronProtParams) -> Validation { + if size == &0 { + return Validation::new() + .with_name("Transaction size".to_string()) + .with_value(false) + .with_description("The transaction size could not be obtained.".to_string()); + } + let res = check_size(&size, &prot_pps); + let description = set_description( + &res, + "The transaction size does not exceed the protocol limit.".to_string(), + ); + return Validation::new() + .with_name("Transaction size".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// & The following validations require the transaction +fn validate_byron_ins_not_empty(tx: &Tx) -> Validation { + let res = check_ins_not_empty(tx); + let description = set_description( + &res, + "The set of transaction inputs is not empty.".to_string(), + ); + return Validation::new() + .with_name("Non empty inputs".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_byron_outs_not_empty(tx: &Tx) -> Validation { + let res = check_outs_not_empty(tx); + let description = set_description( + &res, + "The set of transaction outputs is not empty.".to_string(), + ); + return Validation::new() + .with_name("Non empty outputs".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +fn validate_byron_outs_have_lovelace(tx: &Tx) -> Validation { + let res = check_outs_have_lovelace(tx); + let description = set_description( + &res, + "All transaction outputs contain non-null Lovelace values.".to_string(), + ); + return Validation::new() + .with_name("Outputs have lovelace".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// & The following validations require the transaction and the UTXOs +fn validate_byron_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> Validation { + let res = check_ins_in_utxos(tx, utxos); + let description = set_description( + &res, + "All transaction inputs are in the set of (yet) unspent transaction outputs.".to_string(), + ); + return Validation::new() + .with_name("Inputs in UTXOs".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// & The following validations require the transaction, the UTXOs and the protocol magic +fn validate_byron_witnesses(tx: &MintedTxPayload, utxos: &UTxOs, prot_magic: u32) -> Validation { + let res = check_witnesses(&tx, &utxos, &prot_magic); + let description = set_description(&res, "All transaction witnesses are valid.".to_string()); + return Validation::new() + .with_name("Witnesses".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + +// & The following validations require the transaction, the size, the UTXOs and the protocol parameters +fn validate_byron_fees( + tx: &Tx, + size: &u64, + utxos: &UTxOs, + prot_pps: &ByronProtParams, +) -> Validation { + let res = check_fees(&tx, &size, &utxos, &prot_pps); + let description = set_description( + &res, + "Fees are not less than what is determined by the protocol.".to_string(), + ); + return Validation::new() + .with_name("Fees".to_string()) + .with_value(res.is_ok()) + .with_description(description); +} + pub fn validate_byron(mtxp: &MintedTxPayload) -> Validations { - let out = Validations::new(); + let tx: &Tx = &mtxp.transaction; + let size: &u64 = &get_tx_size(&tx); + let prot_pps: ByronProtParams = ByronProtParams { + fee_policy: FeePolicy { + summand: 155381, + multiplier: 44, + }, + max_tx_size: 16384, + }; + + let out = Validations::new() + .with_era("Byron".to_string()) + .add_new_validation(validate_byron_size(&size, &prot_pps)) + .add_new_validation(validate_byron_ins_not_empty(&tx)) + .add_new_validation(validate_byron_outs_not_empty(&tx)) + .add_new_validation(validate_byron_outs_have_lovelace(&tx)); out } diff --git a/napi-pallas/src/validations/conway.rs b/napi-pallas/src/validations/conway.rs index fa1ca30..1a4a002 100644 --- a/napi-pallas/src/validations/conway.rs +++ b/napi-pallas/src/validations/conway.rs @@ -2,6 +2,6 @@ use pallas::ledger::primitives::conway::MintedTx; use crate::Validations; pub fn validate_conway(mtx_c: &MintedTx) -> Validations { - let out = Validations::new(); + let out = Validations::new().with_era("Conway".to_string()); out } diff --git a/napi-pallas/src/validations/shelley_ma.rs b/napi-pallas/src/validations/shelley_ma.rs index 3778f67..e8c134c 100644 --- a/napi-pallas/src/validations/shelley_ma.rs +++ b/napi-pallas/src/validations/shelley_ma.rs @@ -3,6 +3,6 @@ use pallas::ledger::primitives::alonzo::MintedTx; use crate::Validations; pub fn validate_shelley_ma(mtx_sma: &MintedTx) -> Validations { - let out = Validations::new(); + let out = Validations::new().with_era("Shelley Mary Allegra".to_string()); out } diff --git a/napi-pallas/src/validations/validate.rs b/napi-pallas/src/validations/validate.rs index 8a43c6e..9cfb99a 100644 --- a/napi-pallas/src/validations/validate.rs +++ b/napi-pallas/src/validations/validate.rs @@ -12,7 +12,7 @@ use super::shelley_ma::validate_shelley_ma; pub fn set_description(res: &Result<(), ValidationError>, success: String) -> String { match res { Ok(_) => success, - Err(e) => format!("Error {:?}", e), + Err(e) => format!("Error: {:?}", e), } } diff --git a/web/app/components.tsx b/web/app/components.tsx index d305930..9ee674e 100644 --- a/web/app/components.tsx +++ b/web/app/components.tsx @@ -54,6 +54,7 @@ export function RootSection(props: { data: Section; topics: Record; validations: IValidation[]; + era: string; }) { const [open, setOpen] = useState(false); const handleClick = () => setOpen(!open); @@ -79,7 +80,9 @@ export function RootSection(props: { > {open ? "▼" : "▶"} -

Tx Validations

+
+

Tx Validations - {props.era}

+
{open && } @@ -219,25 +222,32 @@ export function AccordionItem({ validation }: { validation: IValidation }) { onClick={handleClick} > {validation.value ? "✔" : "✘"}  {validation.name} - -
- + -
+ > + + + + -
- {open && ( -

{validation.description}

- )} +
+

{validation.description}

); @@ -247,7 +257,7 @@ export function ValidationAccordion(props: { validations: IValidation[] }) { return (
{props.validations.map((v) => ( diff --git a/web/app/routes/tx.tsx b/web/app/routes/tx.tsx index 1d1b4ac..3d17edf 100644 --- a/web/app/routes/tx.tsx +++ b/web/app/routes/tx.tsx @@ -1,6 +1,6 @@ import { ActionFunctionArgs, json, type MetaFunction } from "@remix-run/node"; import { Form, useActionData } from "@remix-run/react"; -import { Button, logCuriosity, RootSection } from "../components"; +import { Button, RootSection, logCuriosity } from "../components"; import * as server from "./tx.server"; import TOPICS from "./tx.topics"; @@ -12,10 +12,12 @@ export interface IValidation { export interface IValidations { validations: IValidation[]; + era: string; } export interface DataProps extends server.Section { validations: IValidation[]; + era: string; raw?: string; } @@ -65,50 +67,7 @@ export default function Index() { if (data) logCuriosity(data); const validations: IValidation[] = data?.validations || []; - - const validations: IValidation[] = [ - { name: "Non empty inputs", value: true, description: "Sucessful" }, - { - name: "All inputs in utxos", - value: false, - description: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro id maiores exercitationem asperiores molestias assumenda doloremque magnam fugit. Iure dolorum fugit facilis autem incidunt vero necessitatibus consectetur ducimus recusandae blanditiis!", - }, - { name: "Validity interval", value: true, description: "Sucessful" }, - { name: "Fee", value: true, description: "Sucessful" }, - { - name: "Preservation of value", - value: false, - description: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro id maiores exercitationem asperiores molestias assumenda doloremque magnam fugit. Iure dolorum fugit facilis autem incidunt vero necessitatibus consectetur ducimus recusandae blanditiis!", - }, - { - name: "Min lovelace per UTxO", - value: false, - description: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro id maiores exercitationem asperiores molestias assumenda doloremque magnam fugit. Iure dolorum fugit facilis autem incidunt vero necessitatibus consectetur ducimus recusandae blanditiis!", - }, - { name: "Output value size", value: true, description: "Successful" }, - { name: "Network Id", value: true, description: "Successful" }, - { name: "Tx size", value: true, description: "Successful" }, - { name: "Tx execution units", value: true, description: "Successful" }, - { name: "Minting", value: true, description: "Successful" }, - { - name: "Well formed", - value: false, - description: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro id maiores exercitationem asperiores molestias assumenda doloremque magnam fugit. Iure dolorum fugit facilis autem incidunt vero necessitatibus consectetur ducimus recusandae blanditiis!", - }, - { name: "Script witness", value: true, description: "Successful" }, - { name: "Languages", value: true, description: "Successful" }, - { - name: "Auxiliary data hash", - value: false, - description: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro id maiores exercitationem asperiores molestias assumenda doloremque magnam fugit. Iure dolorum fugit facilis autem incidunt vero necessitatibus consectetur ducimus recusandae blanditiis!", - }, - { name: "Script data hash", value: true, description: "Successful" }, - ]; + const era = data?.era || ""; return (
@@ -148,7 +107,12 @@ export default function Index() { )} {!!data && ( - + )}
);