Skip to content

Commit

Permalink
Fetch tx data from blockfrost
Browse files Browse the repository at this point in the history
  • Loading branch information
Quantumplation committed Sep 1, 2024
1 parent cce1a58 commit ae07f66
Show file tree
Hide file tree
Showing 11 changed files with 961 additions and 91 deletions.
679 changes: 650 additions & 29 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion gastronomy-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ publish = false

[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
tokio = { version = "1.40", features = ["full"] }
clap = { version = "4", features = ["derive", "env"] }
gastronomy = { path = "../gastronomy" }
uplc = { git = "https://github.com/SundaeSwap-finance/aiken.git", rev = "e5a60f3" }
# uplc = { path = "../aiken/crates/uplc" }
Expand Down
26 changes: 22 additions & 4 deletions gastronomy-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::path::PathBuf;
use anyhow::Result;
use app::App;
use clap::{command, Parser, Subcommand};
use gastronomy::chain_query::{Blockfrost, None};

mod app;
mod utils;
Expand All @@ -12,25 +13,42 @@ mod utils;
struct Args {
#[command(subcommand)]
command: Option<Commands>,
#[clap(env)]
blockfrost: String,
}

#[derive(Subcommand, Debug)]
enum Commands {
Run {
file: PathBuf,
parameters: Vec<String>,
#[clap(long)]
index: Option<usize>,
},
}

fn main() -> Result<(), anyhow::Error> {
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
utils::install_hooks().unwrap();

let args = Args::parse();

match args.command {
Some(Commands::Run { file, parameters }) => {
let mut raw_programs = gastronomy::uplc::load_programs_from_file(&file)?;
let raw_program = raw_programs.remove(0);
Some(Commands::Run {
file,
parameters,
index,
}) => {
let mut raw_programs = if args.blockfrost.is_empty() {
gastronomy::uplc::load_programs_from_file(&file, None {}).await?
} else {
gastronomy::uplc::load_programs_from_file(
&file,
Blockfrost::new(args.blockfrost.as_str()),
)
.await?
};
let raw_program = raw_programs.remove(index.unwrap_or_default());
let arguments = parameters
.iter()
.enumerate()
Expand Down
39 changes: 29 additions & 10 deletions gastronomy-ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};

use api::{CreateTraceResponse, GetFrameResponse, GetTraceSummaryResponse};
use dashmap::DashMap;
use gastronomy::ExecutionTrace;
use gastronomy::{chain_query::Blockfrost, ExecutionTrace};
use tauri::{InvokeError, Manager, State, Wry};
use tauri_plugin_store::{with_store, StoreBuilder, StoreCollection};

Expand All @@ -16,24 +16,42 @@ struct SessionState {
}

#[tauri::command]
fn create_trace(
async fn create_traces<'a>(
file: &Path,
parameters: Vec<String>,
state: State<SessionState>,
state: State<'a, SessionState>,
app_handle: tauri::AppHandle,
stores: State<'a, StoreCollection<Wry>>,
) -> Result<api::CreateTraceResponse, InvokeError> {
let trace = gastronomy::trace_execution(file, &parameters).map_err(InvokeError::from_anyhow)?;
let identifier = trace.identifier.clone();
state.traces.insert(identifier.clone(), trace);
Ok(CreateTraceResponse {
identifiers: vec![identifier],
})
println!("Creating traces {:?} {:?}", file, parameters);
let path = PathBuf::from("settings.json");

let key = with_store(app_handle, stores, path, |store| {
let key: String = store
.get("blockfrost.key")
.and_then(|v| v.as_str())
.map(|v| v.to_string())
.unwrap_or_default();
Ok(key)
})?;
let mut traces = gastronomy::trace_executions(file, &parameters, Blockfrost::new(key.as_str()))
.await
.unwrap();
let mut identifiers = vec![];
for trace in traces.drain(..) {
let identifier = trace.identifier.clone();
state.traces.insert(identifier.clone(), trace);
identifiers.push(identifier);
}
Ok(CreateTraceResponse { identifiers })
}

#[tauri::command]
fn get_trace_summary(
identifier: &str,
state: State<SessionState>,
) -> Result<api::GetTraceSummaryResponse, InvokeError> {
println!("Getting summary");
let Some(trace) = state.traces.get(identifier) else {
return Err(InvokeError::from("Trace not found"));
};
Expand All @@ -48,6 +66,7 @@ fn get_frame(
frame: usize,
state: State<SessionState>,
) -> Result<api::GetFrameResponse, InvokeError> {
println!("Getting frame");
let Some(trace) = state.traces.get(identifier) else {
return Err(InvokeError::from("Trace not found"));
};
Expand Down Expand Up @@ -84,7 +103,7 @@ fn main() {
traces: DashMap::new(),
})
.invoke_handler(tauri::generate_handler![
create_trace,
create_traces,
get_trace_summary,
get_frame
])
Expand Down
6 changes: 5 additions & 1 deletion gastronomy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ anyhow = "1"
uplc = { git = "https://github.com/SundaeSwap-finance/aiken.git", rev = "e5a60f3" }
# uplc = { path = "../aiken/crates/uplc" }
num-bigint = "0.4"
pallas = "0.30.1"
pallas = { version = "0.30.1", features = ["applying"] }
serde = "1"
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
# pretty = "0.12.3"
hex = "0.4"
blockfrost = "1.0.1"
futures = "0.3.30"
reqwest = "0.12"
168 changes: 168 additions & 0 deletions gastronomy/src/chain_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use anyhow::Result;
use blockfrost::{BlockfrostAPI, JsonValue};
use pallas::{
applying::utils::{add_values, AlonzoError, ValidationError},
codec::utils::{Bytes, CborWrap, NonEmptyKeyValuePairs, PositiveCoin},
ledger::{
addresses::Address,
primitives::conway::{
self, DatumOption, PostAlonzoTransactionOutput, PseudoScript, TransactionOutput,
},
},
};
use serde_json::from_str;
use uplc::{
tx::{ResolvedInput, SlotConfig},
Fragment, Hash, KeyValuePairs, PlutusData, TransactionInput, Value,
};

pub trait ChainQuery {
async fn get_tx_bytes(&self, tx_id: Hash<32>) -> Result<Bytes>;

Check failure on line 20 in gastronomy/src/chain_query.rs

View workflow job for this annotation

GitHub Actions / cargo test

use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
async fn get_utxos(&self, tx_ref: Vec<TransactionInput>) -> Result<Vec<ResolvedInput>>;

Check failure on line 21 in gastronomy/src/chain_query.rs

View workflow job for this annotation

GitHub Actions / cargo test

use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
fn get_slot_config(&self) -> Result<SlotConfig>;
}

pub struct None {}
impl ChainQuery for None {
async fn get_tx_bytes(&self, _tx_id: Hash<32>) -> Result<Bytes> {
unimplemented!("No chain query provider conigured, consider adding a blockfrost API key")
}
async fn get_utxos(&self, _tx_refs: Vec<TransactionInput>) -> Result<Vec<ResolvedInput>> {
unimplemented!("No chain query provider conigured, consider adding a blockfrost API key")
}
fn get_slot_config(&self) -> Result<SlotConfig> {
unimplemented!("No chain query provider conigured, consider adding a blockfrost API key")
}
}

pub struct Blockfrost {
api_key: String,
api: BlockfrostAPI,
}

impl Blockfrost {
pub fn new(api_key: &str) -> Self {
Blockfrost {
api_key: api_key.to_string(),
api: BlockfrostAPI::new(api_key, Default::default()),
}
}
}

impl ChainQuery for Blockfrost {
async fn get_tx_bytes(&self, tx_id: Hash<32>) -> Result<Bytes> {
let client = reqwest::Client::new();
let tx_id = hex::encode(tx_id);
let response = client
.get(format!(
"https://cardano-preview.blockfrost.io/api/v0/txs/{}/cbor",
tx_id
))
.header("project_id", self.api_key.as_str())
.send()
.await?;
let value = from_str::<JsonValue>(&response.text().await?)?;
let tx_bytes = hex::decode(value["cbor"].as_str().unwrap())?;
Ok(tx_bytes.into())
}
async fn get_utxos(&self, inputs: Vec<TransactionInput>) -> Result<Vec<ResolvedInput>> {
let mut resolved_inputs = vec![];
for input in inputs {
let tx = self
.api
.transactions_utxos(hex::encode(input.transaction_id).as_str())
.await?;
let output = tx.outputs[input.index as usize].clone();
let datum = if let Some(datum) = output.inline_datum {
Some(DatumOption::Data(CborWrap(
hex::decode(datum)
.ok()
.and_then(|d| PlutusData::decode_fragment(&d).ok())
.unwrap(),
)))
} else if let Some(hash) = output.data_hash {
Some(DatumOption::Hash(hex::decode(hash).unwrap()[..].into()))
} else {
None
};
let mut value: Value = pallas::applying::utils::empty_value();
for asset in output.amount.iter() {
if asset.unit == "lovelace" {
value = add_values(
&value,
&Value::Coin(asset.quantity.parse().unwrap()),
&ValidationError::Alonzo(AlonzoError::NegativeValue),
)
.unwrap();
} else {
let policy: Hash<28> =
hex::decode(asset.unit[0..56].to_string()).unwrap()[..].into();
let asset_name: Bytes =
hex::decode(asset.unit[56..].to_string()).unwrap().into();
let amount: u64 = asset.quantity.parse().unwrap();
let asset_amt = KeyValuePairs::Def(vec![(asset_name, amount)]);
let multiasset = KeyValuePairs::Def(vec![(policy, asset_amt)]);
value = add_values(
&value,
&Value::Multiasset(0u64, multiasset),
&ValidationError::Alonzo(AlonzoError::NegativeValue),
)
.unwrap();
}
}
let value = match value {
Value::Coin(coin) => conway::Value::Coin(coin),
Value::Multiasset(coin, multiasset) => conway::Value::Multiasset(
coin,
NonEmptyKeyValuePairs::Def(
multiasset
.iter()
.map(|(k, v)| {
(
k.clone(),
NonEmptyKeyValuePairs::Def(
v.iter()
.map(|(k, v)| {
(k.clone(), PositiveCoin::try_from(*v).unwrap())
})
.collect(),
),
)
})
.collect(),
),
),
};

let script_ref = if let Some(script_hash) = output.reference_script_hash {
let script = self
.api
.scripts_datum_hash_cbor(script_hash.as_str())
.await?;
let bytes = hex::decode(script["cbor"].as_str().unwrap()).unwrap();
Some(CborWrap(PseudoScript::PlutusV2Script(
conway::PlutusV2Script(bytes.into()),
)))
} else {
None
};

let output: TransactionOutput =
TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput {
address: Address::from_bech32(&output.address)?.to_vec().into(),
datum_option: datum,
script_ref,
value,
});
resolved_inputs.push(ResolvedInput { input, output });
}
Ok(resolved_inputs)
}
fn get_slot_config(&self) -> Result<SlotConfig> {
Ok(SlotConfig {
zero_time: 1660003200000, // Preview network
zero_slot: 0,
slot_length: 1000,
})
}
}
45 changes: 29 additions & 16 deletions gastronomy/src/execution_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use serde::Serialize;
use uplc::machine::{Context, MachineState};
use uuid::Uuid;

use crate::chain_query::ChainQuery;

pub type Value = String;

#[derive(Serialize)]
Expand Down Expand Up @@ -41,22 +43,33 @@ pub struct ExBudget {
}

impl ExecutionTrace {
pub fn from_file(filename: &Path, parameters: &[String]) -> Result<Self> {
let mut raw_programs = crate::uplc::load_programs_from_file(filename)?;
let raw_program = raw_programs.remove(0);
let arguments = parameters
.iter()
.enumerate()
.map(|(index, param)| crate::uplc::parse_parameter(index, param.clone()))
.collect::<Result<Vec<_>>>()?;
let applied_program = crate::uplc::apply_parameters(raw_program, arguments)?;
let states = crate::uplc::execute_program(applied_program)?;
let frames = parse_frames(&states);
Ok(Self {
identifier: Uuid::new_v4().to_string(),
filename: filename.display().to_string(),
frames,
})
pub async fn from_file(
filename: &Path,
parameters: &[String],
query: impl ChainQuery,
) -> Result<Vec<Self>> {
println!("from file");
let raw_programs = crate::uplc::load_programs_from_file(filename, query).await?;
let mut execution_traces = vec![];

println!("{} programs", raw_programs.len());
for raw_program in raw_programs {
let arguments = parameters
.iter()
.enumerate()
.map(|(index, param)| crate::uplc::parse_parameter(index, param.clone()))
.collect::<Result<Vec<_>>>()?;
let applied_program = crate::uplc::apply_parameters(raw_program, arguments)?;
let states = crate::uplc::execute_program(applied_program)?;

Check failure on line 63 in gastronomy/src/execution_trace.rs

View workflow job for this annotation

GitHub Actions / cargo test

unused variable: `states`
// let frames = parse_frames(&states);
execution_traces.push(Self {
identifier: Uuid::new_v4().to_string(),
filename: filename.display().to_string(),
frames: vec![],
})
}
println!("Done");
Ok(execution_traces)
}
}

Expand Down
Loading

0 comments on commit ae07f66

Please sign in to comment.