Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,245 changes: 910 additions & 335 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ readme = "README.md"
thiserror = "2.0.11"
trait-variant = "0.1.2"
hex = "0.4.3"

pallas = { git = "https://github.com/txpipe/pallas.git", rev = "38546da", features = ["phase2"] }
# pallas = { version = "1.0.0-alpha.2", features = ["phase2"] }
# pallas = { path = "../pallas/pallas", features = ["phase2"] }

dolos-core = { git = "https://github.com/txpipe/dolos.git" }
dolos-cardano = { git = "https://github.com/txpipe/dolos.git" }
# dolos-core = { path = "../dolos/crates/core" }
# dolos-cardano = { path = "../dolos/crates/cardano" }
6 changes: 3 additions & 3 deletions crates/tx3-cardano/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ homepage.workspace = true
readme.workspace = true

[dependencies]
pallas = { version = ">=1.0.0-alpha, <2.0.0" }
# pallas = { version = ">=1.0.0-alpha, <2.0.0", path = "../../../../txpipe/pallas/pallas" }
# pallas = { git = "https://github.com/txpipe/pallas.git" }
pallas = { workspace = true }
dolos-core = { workspace = true }
dolos-cardano = { workspace = true }

hex = "0.4.3"
thiserror = "2.0.11"
Expand Down
16 changes: 8 additions & 8 deletions crates/tx3-cardano/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tx3_lang::{backend::Error, ir};

use crate::{
coercion::{self, expr_into_bytes, expr_into_metadatum, expr_into_number},
Network, PParams, PlutusVersion, EXECUTION_UNITS,
Network, PlutusVersion, EXECUTION_UNITS,
};

pub(crate) mod asset_math;
Expand Down Expand Up @@ -902,11 +902,11 @@ fn infer_plutus_version(witness_set: &primitives::WitnessSet) -> PlutusVersion {

fn compute_script_data_hash(
witness_set: &primitives::WitnessSet,
pparams: &PParams,
cost_models: &CostModels,
) -> Option<primitives::Hash<32>> {
let version = infer_plutus_version(witness_set);

let cost_model = pparams.cost_models.get(&version).unwrap();
let cost_model = cost_models.get(&version).unwrap();

let language_view = primitives::LanguageView(version, cost_model.clone());

Expand Down Expand Up @@ -936,16 +936,16 @@ pub fn compute_min_utxo(
Ok(total_bytes * coins_per_byte)
}

pub fn entry_point(tx: &ir::Tx, pparams: &PParams) -> Result<primitives::Tx<'static>, Error> {
let mut transaction_body = compile_tx_body(tx, pparams.network)?;
let transaction_witness_set = compile_witness_set(tx, &transaction_body, pparams.network)?;
pub fn entry_point(tx: &ir::Tx, network: &Network, cost_models: &CostModels) -> Result<primitives::Tx<'static>, Error> {
let mut transaction_body = compile_tx_body(tx, *network)?;
let transaction_witness_set = compile_witness_set(tx, &transaction_body, *network)?;
let auxiliary_data = compile_auxiliary_data(tx)?;

transaction_body.script_data_hash = compute_script_data_hash(&transaction_witness_set, pparams);
transaction_body.script_data_hash = compute_script_data_hash(&transaction_witness_set, cost_models);

transaction_body.auxiliary_data_hash = auxiliary_data
.as_ref()
.map(|x| primitives::Bytes::from(x.compute_hash().to_vec()));
.map(|x| x.compute_hash());

Ok(primitives::Tx {
transaction_body: transaction_body.into(),
Expand Down
143 changes: 108 additions & 35 deletions crates/tx3-cardano/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap;

pub mod coercion;
Expand All @@ -6,25 +7,28 @@ pub mod compile;
// Re-export pallas for upstream users
pub use pallas;

use pallas::ledger::primitives;
use pallas::ledger::traverse::ComputeHash;
use tx3_lang::backend::TxEval;
use pallas::ledger::{
primitives::{self, RationalNumber, conway::Redeemers, conway::RedeemersKey},
traverse::{ComputeHash, MultiEraTx},
validate::phase2::{
evaluate_tx,
EvalReport,
script_context::SlotConfig,
},
};

use tx3_lang::ir;
use tx3_lang::UtxoRef;
use tx3_lang::backend::{UtxoStore, TxEval};
use dolos_cardano::{PParamsSet, utils::pparams_to_pallas};

#[cfg(test)]
pub mod tests;

pub type Network = pallas::ledger::primitives::NetworkId;
pub type PlutusVersion = u8;
pub type CostModel = Vec<i64>;

pub struct PParams {
pub network: Network,
pub min_fee_coefficient: u64,
pub min_fee_constant: u64,
pub coins_per_utxo_byte: u64,
pub cost_models: HashMap<PlutusVersion, CostModel>,
}
pub type CostModels = HashMap<PlutusVersion, CostModel>;

pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits {
mem: 2000000,
Expand All @@ -50,58 +54,109 @@ pub struct ChainPoint {
}

pub struct Compiler {
pub pparams: PParams,
pub config: Config,
pub latest_tx_body: Option<TxBody>,
pub network: Network,
pub cursor: ChainPoint,
pub pparams: PParamsSet,
pub cost_models: CostModels,
pub slot_config: SlotConfig,
pub latest_tx_body: Option<TxBody>,
}

impl Compiler {
pub fn new(pparams: PParams, config: Config, cursor: ChainPoint) -> Self {
pub fn new(
config: Config,
network: Network,
cursor: ChainPoint,
pparams: PParamsSet,
cost_models: CostModels,
slot_config: SlotConfig,
) -> Self {
Self {
pparams,
config,
latest_tx_body: None,
network,
cursor,
pparams,
cost_models,
slot_config,
latest_tx_body: None,
}
}
}

impl tx3_lang::backend::Compiler for Compiler {
fn compile(&mut self, tx: &tx3_lang::ir::Tx) -> Result<TxEval, tx3_lang::backend::Error> {
let compiled_tx = compile::entry_point(tx, &self.pparams)?;

async fn compile<S: UtxoStore>(&mut self, tx: &tx3_lang::ir::Tx, utxos: &S) -> Result<TxEval, tx3_lang::backend::Error> {
let mut compiled_tx = compile::entry_point(tx, &self.network, &self.cost_models)?;

let multi_era_tx = MultiEraTx::Conway(Box::new(Cow::Owned(compiled_tx.clone())));

// Fetch UTxO dependencies
let utxo_deps = utxos.fetch_utxos_deps(
multi_era_tx
.requires()
.iter()
.map(|r| UtxoRef {
txid: r.hash().to_vec(),
index: r.index() as u32
})
.collect(),
).await?;

// Evaluate the transaction to get the execution units for the redeemers
let reports =
evaluate_tx(&multi_era_tx, &pparams_to_pallas(&self.pparams), &utxo_deps, &self.slot_config)
.map_err(|e| tx3_lang::backend::Error::EvaluationError(e.to_string()))?;

// Update the compiled transaction with the evaluated execution units
for report in &reports {
if let Some(redeemers) = &compiled_tx.transaction_witness_set.redeemer {
match redeemers.clone().unwrap() {
Redeemers::List(mut list) => {
let redeemer = list.iter_mut().find(|r| r.tag == report.tag && r.index == report.index);
if let Some(redeemer) = redeemer {
redeemer.ex_units = report.units.clone();
}
compiled_tx.transaction_witness_set.redeemer = Some(Redeemers::List(list).into());
}
Redeemers::Map(mut map) => {
let key = RedeemersKey { tag: report.tag.clone(), index: report.index };
if let Some(redeemer) = map.get_mut(&key) {
redeemer.ex_units = report.units.clone();
}
compiled_tx.transaction_witness_set.redeemer = Some(Redeemers::Map(map).into());
}
}
}
}

let hash = compiled_tx.transaction_body.compute_hash();
let payload = pallas::codec::minicbor::to_vec(&compiled_tx).unwrap();

self.latest_tx_body = Some(compiled_tx.transaction_body);


// Calculate fees
let size_fees = eval_size_fees(&payload, &self.pparams, self.config.extra_fees);
let redeemers_fees = eval_redeemers_fees(&reports, &self.pparams);

//let redeemer_fees = eval_redeemer_fees(tx, pparams)?;
self.latest_tx_body = Some(compiled_tx.transaction_body);

let eval = TxEval {
Ok(TxEval {
payload,
hash: hash.to_vec(),
fee: size_fees, // TODO: add redeemer fees
ex_units: 0,
};

Ok(eval)
fee: size_fees + redeemers_fees,
})
}

fn execute(&self, op: ir::CompilerOp) -> Result<ir::Expression, tx3_lang::backend::Error> {
match op {
ir::CompilerOp::BuildScriptAddress(x) => {
let hash: primitives::Hash<28> = coercion::expr_into_hash(&x)?;
let address = coercion::policy_into_address(hash.as_ref(), self.pparams.network)?;
let address = coercion::policy_into_address(hash.as_ref(), self.network)?;
Ok(ir::Expression::Address(address.to_vec()))
}
ir::CompilerOp::ComputeMinUtxo(x) => {
let lovelace = compile::compute_min_utxo(
x,
&self.latest_tx_body,
self.pparams.coins_per_utxo_byte as i128,
self.pparams.ada_per_utxo_byte_or_default() as i128,
)?;
Ok(ir::Expression::Assets(vec![ir::AssetExpr {
policy: ir::Expression::None,
Expand Down Expand Up @@ -136,12 +191,30 @@ impl tx3_lang::backend::Compiler for Compiler {
}
}

fn eval_size_fees(tx: &[u8], pparams: &PParams, extra_fees: Option<u64>) -> u64 {
tx.len() as u64 * pparams.min_fee_coefficient
+ pparams.min_fee_constant
fn eval_size_fees(tx: &[u8], pparams: &PParamsSet, extra_fees: Option<u64>) -> u64 {
tx.len() as u64 * pparams.min_fee_a_or_default() as u64
+ pparams.min_fee_b_or_default() as u64
+ extra_fees.unwrap_or(DEFAULT_EXTRA_FEES)
}

fn eval_redeemers_fees(report: &EvalReport, pparams: &PParamsSet) -> u64 {
let primitives::ExUnitPrices { mem_price, step_price } = pparams.execution_costs()
.unwrap_or(primitives::ExUnitPrices {
mem_price: RationalNumber { numerator: 0, denominator: 1 },
step_price: RationalNumber { numerator: 0, denominator: 1 },
});

let mut fees = 0;

for r in report {
fees += (r.units.mem * mem_price.numerator * step_price.denominator
+ r.units.steps * step_price.numerator * mem_price.denominator)
/ (mem_price.denominator * step_price.denominator);
}

fees
}

fn slot_to_time(slot: i128, cursor: &ChainPoint) -> i128 {
let current_time = cursor.timestamp as i128;
let time_diff = slot - cursor.slot as i128;
Expand All @@ -153,4 +226,4 @@ fn time_to_slot(time: i128, cursor: &ChainPoint) -> i128 {
let current_time = cursor.timestamp as i128;
let time_diff = time - current_time;
current_slot + (time_diff / 1000)
}
}
3 changes: 2 additions & 1 deletion crates/tx3-lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ readme.workspace = true
thiserror = { workspace = true }
trait-variant = { workspace = true }
hex = { workspace = true }
pallas = { workspace = true }

miette = { version = "7.4.0", features = ["fancy"] }
miette = { version = "7.6.0", features = ["fancy"] }
pest = { version = "2.7.15", features = ["miette-error", "pretty-print"] }
pest_derive = "2.7.15"
serde = { version = "1.0.217", features = ["derive"] }
Expand Down
11 changes: 8 additions & 3 deletions crates/tx3-lang/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::collections::HashSet;

use crate::{applying, ir, UtxoRef, UtxoSet};

use pallas::ledger::validate::utils::UtxoMap;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("transient error: {0}")]
Expand Down Expand Up @@ -42,18 +44,20 @@ pub enum Error {

#[error("can't reduce {0:?}")]
CantReduce(ir::CompilerOp),

#[error("tx evaluation failed: {0}")]
EvaluationError(String),
}

#[derive(Debug, PartialEq)]
pub struct TxEval {
pub payload: Vec<u8>,
pub hash: Vec<u8>,
pub payload: Vec<u8>,
pub fee: u64,
pub ex_units: u64,
}

pub trait Compiler {
fn compile(&mut self, tx: &ir::Tx) -> Result<TxEval, Error>;
async fn compile<S: UtxoStore>(&mut self, tx: &ir::Tx, utxos: &S) -> Result<TxEval, Error>;
fn execute(&self, op: ir::CompilerOp) -> Result<ir::Expression, Error>;
}

Expand Down Expand Up @@ -90,4 +94,5 @@ impl<'a> UtxoPattern<'a> {
pub trait UtxoStore {
async fn narrow_refs(&self, pattern: UtxoPattern) -> Result<HashSet<UtxoRef>, Error>;
async fn fetch_utxos(&self, refs: HashSet<UtxoRef>) -> Result<UtxoSet, Error>;
async fn fetch_utxos_deps(&self, refs: HashSet<UtxoRef>) -> Result<UtxoMap, Error>;
}
4 changes: 2 additions & 2 deletions crates/tx3-resolver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use tx3_lang::{
applying::{self, Apply as _},
backend::{self, Compiler, TxEval, UtxoStore},
backend::{self, Compiler, UtxoStore, TxEval},
ir::{self, Node},
};

Expand Down Expand Up @@ -50,7 +50,7 @@ async fn eval_pass<C: Compiler, S: UtxoStore>(
return Err(Error::CantCompileNonConstantTir);
}

let eval = compiler.compile(attempt.as_ref())?;
let eval = compiler.compile(attempt.as_ref(), utxos).await?;

let Some(last_eval) = last_eval else {
return Ok(Some(eval));
Expand Down
Loading