Skip to content
Merged
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 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ target/
#.idea/

.scratchpad/
**/.DS_Store

.opencode
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/tx3-cardano/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub fn expr_into_metadatum(
pub fn expr_into_utxo_refs(expr: &tir::Expression) -> Result<Vec<UtxoRef>, Error> {
match expr {
tir::Expression::UtxoRefs(x) => Ok(x.clone()),
tir::Expression::UtxoSet(x) => Ok(x.iter().map(|x| x.r#ref.clone()).collect()),
tir::Expression::UtxoSet(x) => Ok(x.refs_sorted()),
tir::Expression::String(x) => {
let (raw_txid, raw_output_ix) = x.split_once("#").expect("Invalid utxo ref");
Ok(vec![UtxoRef {
Expand Down
7 changes: 5 additions & 2 deletions crates/tx3-cardano/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub type Network = pallas::ledger::primitives::NetworkId;
pub type PlutusVersion = u8;
pub type CostModel = Vec<i64>;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PParams {
pub network: Network,
pub min_fee_coefficient: u64,
Expand All @@ -38,24 +39,26 @@ pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits {
const DEFAULT_EXTRA_FEES: u64 = 200_000;
const MIN_UTXO_BYTES: i128 = 197;

#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Config {
pub extra_fees: Option<u64>,
}

pub type TxBody =
pallas::codec::utils::KeepRaw<'static, primitives::conway::TransactionBody<'static>>;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChainPoint {
pub slot: u64,
pub hash: Vec<u8>,
pub timestamp: u128,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Compiler {
pub pparams: PParams,
pub config: Config,
#[serde(skip)]
pub latest_tx_body: Option<TxBody>,
pub cursor: ChainPoint,
}
Expand Down
14 changes: 7 additions & 7 deletions crates/tx3-cardano/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::{BTreeMap, HashMap};

use tx3_lang::Workspace;
use tx3_tir::compile::{CompiledTx, Compiler as _};
use tx3_tir::model::assets::CanonicalAssets;
use tx3_tir::model::core::{Utxo, UtxoRef};
use tx3_tir::model::core::{Utxo, UtxoRef, UtxoSet};
use tx3_tir::model::v1beta0 as tir;
use tx3_tir::reduce::{self, Apply as _, ArgValue};
use tx3_tir::Node as _;
Expand Down Expand Up @@ -79,7 +79,7 @@ fn address_to_bytes(address: &str) -> ArgValue {
)
}

fn wildcard_utxos(datum: Option<tir::Expression>) -> HashSet<Utxo> {
fn wildcard_utxos(datum: Option<tir::Expression>) -> UtxoSet {
let tx_hash = hex::decode("267aae354f0d14d82877fa5720f7ddc9b0e3eea3cd2a0757af77db4d975ba81c")
.unwrap()
.try_into()
Expand All @@ -98,10 +98,10 @@ fn wildcard_utxos(datum: Option<tir::Expression>) -> HashSet<Utxo> {
script: None,
};

HashSet::from([utxo])
[utxo].into()
}

fn fill_inputs(tx: tir::Tx, utxos: HashSet<Utxo>) -> tir::Tx {
fn fill_inputs(tx: tir::Tx, utxos: UtxoSet) -> tir::Tx {
let mut tx = tx;

for (name, _) in reduce::find_queries(&tx) {
Expand All @@ -120,7 +120,7 @@ fn compile_tx_round(
args: &BTreeMap<String, ArgValue>,
fees: u64,
compiler: &mut Compiler,
utxos: HashSet<Utxo>,
utxos: UtxoSet,
) -> CompiledTx {
tx = reduce::apply_args(tx, args).unwrap();
tx = reduce::apply_fees(tx, fees).unwrap();
Expand All @@ -138,7 +138,7 @@ fn test_compile(
tx: tir::Tx,
args: &BTreeMap<String, ArgValue>,
compiler: &mut Compiler,
utxos: HashSet<Utxo>,
utxos: UtxoSet,
) -> CompiledTx {
let mut fees = 0;
let mut rounds = 0;
Expand Down
1 change: 1 addition & 0 deletions crates/tx3-resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pollster = { version = "0.4.0", features = ["macro"] }
chainfuzz = { version = "0.2.0" }
proptest = "1.7.0"
jsonschema = "0.17.1"
tx3-cardano = { path = "../tx3-cardano" }

[features]
naive_selector = []
218 changes: 218 additions & 0 deletions crates/tx3-resolver/src/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use std::path::Path;

use serde_json::{json, Map, Value};

use tx3_tir::compile::CompiledTx;
use tx3_tir::encoding::AnyTir;
use tx3_tir::model::assets::CanonicalAssets;
use tx3_tir::model::core::{Utxo, UtxoRef};
use tx3_tir::reduce::ArgValue;

use crate::inputs::CanonicalQuery;
use crate::interop::{self, TirEnvelope};
use crate::job::{QueryResolution, ResolveJob, ResolveLog, ResolveLogEntry};

pub trait DiagnosticDump {
fn to_dump(&self) -> Value;
}

// ---------------------------------------------------------------------------
// Leaf types
// ---------------------------------------------------------------------------

impl DiagnosticDump for UtxoRef {
fn to_dump(&self) -> Value {
interop::utxo_ref_to_json(self)
}
}

impl DiagnosticDump for AnyTir {
fn to_dump(&self) -> Value {
let envelope = TirEnvelope::from(self.clone());
serde_json::to_value(envelope).expect("TirEnvelope should serialize")
}
}

impl DiagnosticDump for CompiledTx {
fn to_dump(&self) -> Value {
json!({
"payload": hex::encode(&self.payload),
"hash": hex::encode(&self.hash),
"fee": self.fee,
"ex_units": self.ex_units,
})
}
}

impl DiagnosticDump for ArgValue {
fn to_dump(&self) -> Value {
interop::arg_to_json(self)
}
}

impl DiagnosticDump for CanonicalAssets {
fn to_dump(&self) -> Value {
let map: Map<String, Value> = self
.iter()
.map(|(class, amount)| (class.to_string(), json!(amount)))
.collect();
Value::Object(map)
}
}

impl DiagnosticDump for Utxo {
fn to_dump(&self) -> Value {
interop::utxo_to_json(self)
}
}

// ---------------------------------------------------------------------------
// Canonical query
// ---------------------------------------------------------------------------

impl DiagnosticDump for CanonicalQuery {
fn to_dump(&self) -> Value {
json!({
"address": self.address.as_ref().map(|a| hex::encode(a)),
"min_amount": self.min_amount.as_ref().map(|a| a.to_dump()),
"refs": self.refs.iter().map(|r| r.to_dump()).collect::<Vec<_>>(),
"support_many": self.support_many,
"collateral": self.collateral,
})
}
}

// ---------------------------------------------------------------------------
// Resolve pipeline types
// ---------------------------------------------------------------------------

impl DiagnosticDump for QueryResolution {
fn to_dump(&self) -> Value {
json!({
"name": self.name,
"query": self.query.to_dump(),
"selection": self.selection.as_ref().map(|sel| {
sel.iter().map(|u| u.to_dump()).collect::<Vec<_>>()
}),
})
}
}

impl DiagnosticDump for ResolveLog {
fn to_dump(&self) -> Value {
match self {
ResolveLog::ArgsApplied(tir) => json!({"args_applied": tir.to_dump()}),
ResolveLog::FeesApplied(tir) => json!({"fees_applied": tir.to_dump()}),
ResolveLog::CompilerApplied(tir) => json!({"compiler_applied": tir.to_dump()}),
ResolveLog::Reduced(tir) => json!({"reduced": tir.to_dump()}),
ResolveLog::InputsResolved(tir) => json!({"inputs_resolved": tir.to_dump()}),
ResolveLog::FinalReduced(tir) => json!({"final_reduced": tir.to_dump()}),
ResolveLog::Compiled(tx) => json!({"compiled": tx.to_dump()}),
ResolveLog::Converged => json!("converged"),
}
}
}

impl DiagnosticDump for ResolveLogEntry {
fn to_dump(&self) -> Value {
json!({
"round": self.round,
"event": self.event.to_dump(),
})
}
}

// ---------------------------------------------------------------------------
// ResolveJob — top-level
// ---------------------------------------------------------------------------

impl DiagnosticDump for ResolveJob {
fn to_dump(&self) -> Value {
let args: Map<String, Value> = self
.args
.iter()
.map(|(k, v)| (k.clone(), interop::arg_to_json(v)))
.collect();

let input_pool = self.input_pool.as_ref().map(|pool| {
let map: Map<String, Value> = pool
.iter()
.map(|(r, u)| {
let key = format!("{}#{}", hex::encode(&r.txid), r.index);
(key, interop::utxo_to_json(u))
})
.collect();
Value::Object(map)
});

json!({
"original_tir": self.original_tir.to_dump(),
"args": args,
"compiler": &self.compiler,
"round": self.round,
"last_eval": self.last_eval.as_ref().map(|e| e.to_dump()),
"converged": self.converged,
"log": self.log.iter().map(|e| e.to_dump()).collect::<Vec<_>>(),
"input_queries": self.input_queries.iter().map(|q| q.to_dump()).collect::<Vec<_>>(),
"input_pool": input_pool,
})
}
}

pub fn dump_to_dir(job: &ResolveJob, dir: &Path) -> Result<String, std::io::Error> {
std::fs::create_dir_all(dir)?;
let dump_id = uuid::Uuid::new_v4().to_string();
let path = dir.join(format!("resolve-job-{dump_id}.json"));
let file = std::fs::File::create(&path)?;
serde_json::to_writer_pretty(file, &job.to_dump())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
tracing::debug!(dump_id = %dump_id, path = %path.display(), "diagnostic dump written");
Ok(dump_id)
}

#[cfg(test)]
mod tests {
use tx3_tir::encoding::AnyTir;
use tx3_tir::model::v1beta0 as tir;
use tx3_tir::reduce::ArgMap;

use crate::{
dump::dump_to_dir,
job::{ResolveJob, ResolveLog},
};

fn dummy_tir() -> AnyTir {
AnyTir::V1Beta0(tir::Tx {
fees: tir::Expression::None,
references: vec![],
inputs: vec![],
outputs: vec![],
validity: None,
mints: vec![],
burns: vec![],
adhoc: vec![],
collateral: vec![],
signers: None,
metadata: vec![],
})
}

#[test]
fn dump_creates_valid_json_file() {
let mut job = ResolveJob::new(dummy_tir(), ArgMap::new());
job.record(ResolveLog::ArgsApplied(dummy_tir()));
job.record(ResolveLog::Converged);

let dir = std::env::temp_dir().join("tx3-test-dump");
let dump_id = dump_to_dir(&job, &dir).expect("dump should succeed");

let path = dir.join(format!("resolve-job-{dump_id}.json"));
assert!(path.exists());

let contents = std::fs::read_to_string(&path).unwrap();
let value: serde_json::Value = serde_json::from_str(&contents).unwrap();
assert!(value.get("compiler").is_some());

let _ = std::fs::remove_file(&path);
}
}
2 changes: 1 addition & 1 deletion crates/tx3-resolver/src/inputs/approximate/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ mod tests {
use proptest::prelude::*;
use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef};

use crate::inputs::test_utils::{
use crate::test_utils::{
self, any_address, any_utxo, query, utxo, utxo_with_asset, utxo_with_ref,
};

Expand Down
Loading
Loading