Skip to content

Commit

Permalink
feat: utxo set parsing for validations
Browse files Browse the repository at this point in the history
  • Loading branch information
mikarasv committed May 21, 2024
1 parent 73b4110 commit fa360ae
Show file tree
Hide file tree
Showing 10 changed files with 528 additions and 117 deletions.
7 changes: 4 additions & 3 deletions napi-pallas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Section {
self
}

fn build_child<F>(mut self, func: F) -> Self
fn build_child<F>(self, func: F) -> Self
where
F: FnOnce() -> Section,
{
Expand Down Expand Up @@ -218,9 +218,10 @@ pub struct SectionValidation {
pub validations: Validations,
}

#[tokio::main]
#[napi]
pub fn safe_parse_tx(raw: String, context: ValidationContext) -> SectionValidation {
match tx::parse(raw, context) {
pub async fn safe_parse_tx(raw: String, context: ValidationContext) -> SectionValidation {
match tx::parse(raw, context).await {
Ok(x) => {
let (section, validations) = x;
SectionValidation {
Expand Down
108 changes: 75 additions & 33 deletions napi-pallas/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use crate::{ProtocolParams, Validations};

use super::Section;
use blockfrost::{BlockFrostSettings, BlockfrostAPI};
use blockfrost_openapi::models::tx_content_utxo_inputs_inner::TxContentUtxoInputsInner;
use dotenv::dotenv;
use num_rational::Rational32;
use num_rational::Rational64;
use num_traits::FromPrimitive;
use pallas::ledger::traverse::Era;
use pallas::{
Expand Down Expand Up @@ -241,7 +242,10 @@ pub fn create_cbor_structure(tx: &MultiEraTx<'_>) -> Section {
out
}

pub fn parse(raw: String, context: ValidationContext) -> Result<(Section, Validations), Section> {
pub async fn parse(
raw: String,
context: ValidationContext,
) -> Result<(Section, Validations), Section> {
let res_cbor = hex::decode(raw);
let mut era_decode = Era::Babbage;
match context.era.as_str() {
Expand All @@ -257,7 +261,7 @@ pub fn parse(raw: String, context: ValidationContext) -> Result<(Section, Valida
Ok(cbor) => {
let res_mtx = MultiEraTx::decode_for_era(era_decode, &cbor);
match res_mtx {
Ok(mtx) => Ok((create_cbor_structure(&mtx), validate(&mtx, context))),
Ok(mtx) => Ok((create_cbor_structure(&mtx), validate(&mtx, context).await)),
Err(e) => {
let mut err = Section::new();
err.error = Some(e.to_string());
Expand All @@ -273,21 +277,21 @@ pub fn parse(raw: String, context: ValidationContext) -> Result<(Section, Valida
}
}

fn to_fraction(value: f32) -> (i32, i32) {
let rational = Rational32::from_f32(value).unwrap_or_else(|| Rational32::new(0, 1));
fn to_fraction(value: f32) -> (i64, i64) {
let rational = Rational64::from_f32(value).unwrap_or_else(|| Rational64::new(0, 1));
let (numerator, denominator) = rational.into();

(numerator, denominator)
}

fn parse_param_to_i64(value: &str) -> i64 {
fn parse_string_to_i64(value: String) -> i64 {
match value.parse::<i64>() {
Ok(num) => num,
Err(_) => 0,
}
}

fn parse_option_param_to_i64(value: Option<String>) -> i64 {
fn parse_option_string_to_i64(value: Option<String>) -> i64 {
match value {
Some(value) => match value.parse::<i64>() {
Ok(num) => num,
Expand All @@ -297,7 +301,7 @@ fn parse_option_param_to_i64(value: Option<String>) -> i64 {
}
}

fn parse_option_param_to_u32(value: Option<String>) -> u32 {
fn parse_option_string_to_u32(value: Option<String>) -> u32 {
match value {
Some(value) => match value.parse::<u32>() {
Ok(num) => num,
Expand All @@ -307,6 +311,13 @@ fn parse_option_param_to_u32(value: Option<String>) -> u32 {
}
}

fn parse_option_i32_to_u32(value: Option<i32>) -> u32 {
match value {
Some(value) => value as u32,
None => 0,
}
}

pub async fn get_epochs_latest_parameters(
network: String,
) -> Result<ProtocolParams, ProtocolParams> {
Expand All @@ -324,38 +335,43 @@ pub async fn get_epochs_latest_parameters(
match epochs_latest_parameters {
Ok(params) => {
let mut out = ProtocolParams::new();
let parsed_key_deposit: i64 = parse_param_to_i64(&params.key_deposit);
let parsed_pool_deposit: i64 = parse_param_to_i64(&params.pool_deposit);
let parsed_extra_entropy: f32 = match params.extra_entropy {
let parsed_key_deposit = parse_string_to_i64(params.key_deposit);
let parsed_pool_deposit = parse_string_to_i64(params.pool_deposit);
let parsed_extra_entropy = match params.extra_entropy {
Some(value) => match value.parse::<f32>() {
Ok(num) => num,
Err(_) => 0.0,
},
None => 0.0,
};
let parsed_max_tx_ex_mem: u32 = parse_option_param_to_u32(params.max_tx_ex_mem);
let parsed_max_tx_ex_steps: i64 = parse_option_param_to_i64(params.max_tx_ex_steps);
let parsed_max_block_ex_mem: u32 = parse_option_param_to_u32(params.max_block_ex_mem);
let parsed_max_block_ex_steps: i64 = parse_option_param_to_i64(params.max_block_ex_steps);

let parsed_max_val_size: u32 = parse_option_param_to_u32(params.max_val_size);
let parsed_collateral_percent = match params.collateral_percent {
Some(value) => value as u32,
None => 0,
let parsed_price_mem = match params.price_mem {
Some(value) => value,
None => 0.0,
};
let parsed_max_collateral_inputs: u32 = match params.max_collateral_inputs {
Some(value) => value as u32,
None => 0,
let parsed_price_step = match params.price_step {
Some(value) => value,
None => 0.0,
};
let parsed_coins_per_utxo_size: i64 = parse_option_param_to_i64(params.coins_per_utxo_size);
let parsed_coins_per_utxo_word: i64 = parse_option_param_to_i64(params.coins_per_utxo_word);
let parsed_min_utxo = parse_string_to_i64(params.min_utxo);
let parsed_min_pool_cost = parse_string_to_i64(params.min_pool_cost);
let parsed_max_tx_ex_mem = parse_option_string_to_u32(params.max_tx_ex_mem);
let parsed_max_tx_ex_steps = parse_option_string_to_i64(params.max_tx_ex_steps);
let parsed_max_block_ex_mem = parse_option_string_to_u32(params.max_block_ex_mem);
let parsed_max_block_ex_steps = parse_option_string_to_i64(params.max_block_ex_steps);
let parsed_max_val_size = parse_option_string_to_u32(params.max_val_size);
let parsed_collateral_percent = parse_option_i32_to_u32(params.collateral_percent);
let parsed_max_collateral_inputs = parse_option_i32_to_u32(params.max_collateral_inputs);
let parsed_coins_per_utxo_size = parse_option_string_to_i64(params.coins_per_utxo_size);
let parsed_coins_per_utxo_word = parse_option_string_to_i64(params.coins_per_utxo_word);

let (a0_numerator, a0_denominator) = to_fraction(params.a0);
let (rho_numerator, rho_denominator) = to_fraction(params.rho);
let (tau_numerator, tau_denominator) = to_fraction(params.tau);
let (decentralisation_param_numerator, decentralisation_param_denominator) =
to_fraction(params.decentralisation_param);
let (extra_entropy_numerator, extra_entropy_denominator) = to_fraction(parsed_extra_entropy);
let (price_mem_numerator, price_mem_denominator) = to_fraction(parsed_price_mem);
let (price_step_numerator, price_step_denominator) = to_fraction(parsed_price_step);

out.epoch = params.epoch as u32;
out.min_fee_a = params.min_fee_a as u32;
Expand All @@ -367,16 +383,24 @@ pub async fn get_epochs_latest_parameters(
out.pool_deposit = parsed_pool_deposit;
out.e_max = params.e_max as i64;
out.n_opt = params.n_opt as u32;
out.a0_numerator = a0_numerator as i64;
out.a0_denominator = a0_denominator as i64;
out.rho_numerator = rho_numerator as i64;
out.rho_denominator = rho_denominator as i64;
out.tau_numerator = tau_numerator as i64;
out.tau_denominator = tau_denominator as i64;
out.decentralisation_param_numerator = decentralisation_param_numerator as i64;
out.decentralisation_param_denominator = decentralisation_param_denominator as i64;
out.a0_numerator = a0_numerator;
out.a0_denominator = a0_denominator;
out.rho_numerator = rho_numerator;
out.rho_denominator = rho_denominator;
out.tau_numerator = tau_numerator;
out.tau_denominator = tau_denominator;
out.decentralisation_param_numerator = decentralisation_param_numerator;
out.decentralisation_param_denominator = decentralisation_param_denominator;
out.extra_entropy_numerator = extra_entropy_numerator as u32;
out.extra_entropy_denominator = extra_entropy_denominator as u32;
out.protocol_major_ver = params.protocol_major_ver as i64;
out.protocol_minor_ver = params.protocol_minor_ver as i64;
out.min_utxo = parsed_min_utxo;
out.min_pool_cost = parsed_min_pool_cost;
out.price_mem_numerator = price_mem_numerator;
out.price_mem_denominator = price_mem_denominator;
out.price_step_numerator = price_step_numerator;
out.price_step_denominator = price_step_denominator;
out.max_tx_ex_mem = parsed_max_tx_ex_mem;
out.max_tx_ex_steps = parsed_max_tx_ex_steps;
out.max_block_ex_mem = parsed_max_block_ex_mem;
Expand All @@ -396,3 +420,21 @@ pub async fn get_epochs_latest_parameters(
}
}
}

pub async fn get_inputs(hash: String, network: String) -> Vec<TxContentUtxoInputsInner> {
let settings = BlockFrostSettings::new();
dotenv().ok();
let mut project_id = env::var("MAINNET_PROJECT_ID").expect("MAINNET_PROJECT_ID must be set.");
if network == "Preprod" {
project_id = env::var("PREPROD_PROJECT_ID").expect("PREPROD_PROJECT_ID must be set.");
} else if network == "Preview" {
project_id = env::var("PREVIEW_PROJECT_ID").expect("PREVIEW_PROJECT_ID must be set.");
}

let api = BlockfrostAPI::new(&project_id, settings);
let tx_content = api.transactions_utxos(&hash).await;
match tx_content {
Ok(tx_) => tx_.inputs,
Err(_) => Vec::new(),
}
}
116 changes: 110 additions & 6 deletions napi-pallas/src/validations/alonzo.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{borrow::Cow, iter::zip, str::FromStr};

use pallas::{
applying::{
alonzo::{
Expand All @@ -9,13 +11,18 @@ use pallas::{
utils::{get_alonzo_comp_tx_size, AlonzoProtParams},
Environment, MultiEraProtocolParameters, UTxOs,
},
ledger::primitives::{
alonzo::{ExUnitPrices, Language, MintedTx, TransactionBody},
conway::{ExUnits, Nonce, NonceVariant, RationalNumber},
codec::utils::Bytes,
crypto::hash::Hash,
ledger::{
primitives::{
alonzo::{ExUnitPrices, Language, MintedTx, TransactionBody, TransactionOutput, Value},
conway::{ExUnits, Nonce, NonceVariant, RationalNumber},
},
traverse::{MultiEraInput, MultiEraOutput, OriginalHash},
},
};

use crate::{Validation, ValidationContext, Validations};
use crate::{tx::get_inputs, Validation, ValidationContext, Validations};

use super::validate::set_description;
use pallas::codec::utils::KeyValuePairs;
Expand Down Expand Up @@ -234,7 +241,33 @@ fn validate_alonzo_fee(mtx_a: &MintedTx, utxos: &UTxOs, prot_pps: &AlonzoProtPar
}
}

pub fn validate_alonzo(mtx_a: &MintedTx, context: ValidationContext) -> Validations {
pub fn mk_utxo_for_alonzo_compatible_tx<'a>(
tx_body: &TransactionBody,
tx_outs_info: &Vec<(
String, // address in string format
Value,
Option<Hash<32>>,
)>,
) -> UTxOs<'a> {
let mut utxos: UTxOs = UTxOs::new();
for (tx_in, (address, amount, datum_hash)) in zip(tx_body.inputs.clone(), tx_outs_info) {
let multi_era_in = MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
let address_bytes = match hex::decode(hex::encode(address)) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => return UTxOs::new(),
};
let tx_out = TransactionOutput {
address: address_bytes,
amount: amount.clone(),
datum_hash: *datum_hash,
};
let multi_era_out = MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}
utxos
}

pub async fn validate_alonzo(mtx_a: &MintedTx<'_>, context: ValidationContext) -> Validations {
let tx_body: &TransactionBody = &mtx_a.transaction_body;
let ppt_params = context.protocol_params;
let size: &Option<u32> = &get_alonzo_comp_tx_size(tx_body);
Expand Down Expand Up @@ -316,6 +349,73 @@ pub fn validate_alonzo(mtx_a: &MintedTx, context: ValidationContext) -> Validati
block_slot: context.block_slot as u64,
network_id: net_id,
};

let inputs = get_inputs(
mtx_a.transaction_body.original_hash().to_string(),
context.network.clone(),
)
.await;
let mut tx_outs_info = vec![];
inputs.iter().for_each(|tx_in| {
let address = &tx_in.address;
let mut lovelace_am = 0;
let mut assets: Vec<(Hash<28>, Vec<(Bytes, u64)>)> = vec![];
for amt in &tx_in.amount {
match amt.quantity.parse::<u64>() {
Ok(a) => {
if amt.unit == "lovelace" {
lovelace_am += a
} else {
let policy: Hash<28> = match Hash::<28>::from_str(&amt.unit[..56]) {
Ok(hash) => hash,
Err(_) => Hash::new([0; 28]),
};
let asset_name = Bytes::from(hex::decode(amt.unit[56..].to_string()).unwrap());
if let Some((_, policies)) = assets.iter_mut().find(|(hash, _)| hash == &policy) {
// If found, append (asset name, amount) to assets
policies.push((asset_name, amt.quantity.parse::<u64>().unwrap()));
} else {
// If not found, add a new tuple (policy, (asset name, amount)) to assets
assets.push((
policy,
vec![(asset_name, amt.quantity.parse::<u64>().unwrap())],
));
}
}
}
Err(_) => {
// TODO: Handle error appropriately
continue; // Skip this iteration if parsing fails
}
}
}

let datum_opt = match &tx_in.data_hash {
Some(data_hash) => Some(hex::decode(data_hash).unwrap().as_slice().into()),
_ => None,
};
if assets.len() > 0 {
let transformed_assets: Vec<(Hash<28>, KeyValuePairs<Bytes, u64>)> = assets
.into_iter()
.map(|(hash, vec)| {
let kv_pairs = KeyValuePairs::from(Vec::from(vec));
(hash, kv_pairs)
})
.collect();
tx_outs_info.push((
address.clone(),
Value::Multiasset(
lovelace_am,
KeyValuePairs::from(Vec::from(transformed_assets)),
),
datum_opt,
));
} else {
tx_outs_info.push((address.clone(), Value::Coin(lovelace_am), datum_opt));
}
});
let utxos = mk_utxo_for_alonzo_compatible_tx(&mtx_a.transaction_body, &tx_outs_info);

let out = Validations::new()
.with_era("Alonzo".to_string())
.add_new_validation(validate_alonzo_tx_size(size, &prot_params))
Expand All @@ -328,6 +428,10 @@ pub fn validate_alonzo(mtx_a: &MintedTx, context: ValidationContext) -> Validati
.add_new_validation(validate_alonzo_tx_ex_units(mtx_a, &prot_params))
.add_new_validation(validate_alonzo_languages(mtx_a, &prot_params))
.add_new_validation(validate_alonzo_network_id(mtx_a, &env.network_id))
.add_new_validation(validate_alonzo_tx_validity_interval(mtx_a, &env.block_slot));
.add_new_validation(validate_alonzo_tx_validity_interval(mtx_a, &env.block_slot))
.add_new_validation(validate_alonzo_ins_and_collateral_in_utxos(mtx_a, &utxos))
.add_new_validation(validate_alonzo_preservation_of_value(mtx_a, &utxos))
.add_new_validation(validate_alonzo_witness_set(mtx_a, &utxos))
.add_new_validation(validate_alonzo_fee(mtx_a, &utxos, &prot_params));
out
}
Loading

0 comments on commit fa360ae

Please sign in to comment.