From 465006f4a69ac781614cbc3eee07bf0a85da7c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Mon, 5 May 2025 20:05:44 +0300 Subject: [PATCH 01/22] Implement tx submit evaluation via pallas --- Cargo.lock | 287 +++++++++--- Cargo.toml | 4 +- src/api/tx/submit.rs | 46 +- src/api/utils/txs/evaluate.rs | 1 + src/api/utils/txs/evaluate/model.rs | 306 ++++++++++++ src/api/utils/txs/evaluate/root.rs | 28 +- src/api/utils/txs/evaluate/utxos.rs | 22 +- src/cbor.rs | 1 + src/cbor/evaluate.rs | 652 ++++++++++++++++++++++++++ src/common.rs | 37 ++ src/node.rs | 1 + src/node/ledger.rs | 53 +++ tests/api/utils.rs | 6 + tests/api/utils/txs/evaluate/root.rs | 35 ++ tests/api/utils/txs/evaluate/utxos.rs | 66 +++ tests/common.rs | 6 + tests/endpoints.rs | 7 + tests/endpoints_test.rs | 1 + 18 files changed, 1446 insertions(+), 113 deletions(-) create mode 100644 src/api/utils/txs/evaluate/model.rs create mode 100644 src/cbor/evaluate.rs create mode 100644 src/node/ledger.rs create mode 100644 tests/api/utils.rs create mode 100644 tests/api/utils/txs/evaluate/root.rs create mode 100644 tests/api/utils/txs/evaluate/utxos.rs create mode 100644 tests/endpoints.rs diff --git a/Cargo.lock b/Cargo.lock index 28330a82..3c691eab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -120,6 +126,12 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.88" @@ -200,6 +212,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.74" @@ -285,7 +303,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.13.0", "rand_core 0.6.4", "serde", "unicode-normalization", @@ -312,6 +330,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + [[package]] name = "bitcoin_hashes" version = "0.13.0" @@ -319,7 +343,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ "bitcoin-internals", - "hex-conservative", + "hex-conservative 0.1.2", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", ] [[package]] @@ -456,6 +490,18 @@ dependencies = [ "zip", ] +[[package]] +name = "blst" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -575,6 +621,19 @@ dependencies = [ "windows-link", ] +[[package]] +name = "chumsky" +version = "1.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7b80276986f86789dc56ca6542d53bba9cda3c66091ebbe7bd96fc1bdf20f1f" +dependencies = [ + "hashbrown 0.14.5", + "regex-automata 0.3.9", + "serde", + "stacker", + "unicode-ident", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1302,6 +1361,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "h2" version = "0.4.9" @@ -1344,6 +1413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", ] [[package]] @@ -1394,6 +1464,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1546,7 +1625,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.58.0", ] [[package]] @@ -1843,9 +1922,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libproc" version = "0.14.10" @@ -2013,6 +2098,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "minicbor" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0452a60c1863c1f50b5f77cd295e8d2786849f35883f0b9e18e7e6e1b5691b0" +dependencies = [ + "minicbor-derive 0.15.3", +] + [[package]] name = "minicbor" version = "0.26.4" @@ -2020,7 +2114,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acb9d59e79ad66121ab441a0d1950890906a41e01ae14145ecf8401aa8894f50" dependencies = [ "half", - "minicbor-derive", + "minicbor-derive 0.16.2", +] + +[[package]] +name = "minicbor-derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2209fff77f705b00c737016a48e73733d7fbccb8b007194db148f03561fb70" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -2365,7 +2470,7 @@ version = "1.0.0-alpha.1" source = "git+https://github.com/blockfrost/pallas.git?tag=blockfrost-platform-0.0.3-alpha2#fa233715b67b22fd8b191c9294858a3185e1207b" dependencies = [ "hex", - "minicbor", + "minicbor 0.26.4", "serde", "thiserror 1.0.69", ] @@ -2507,9 +2612,11 @@ dependencies = [ "pallas-crypto", "pallas-primitives", "pallas-traverse", + "rug", "serde", "thiserror 1.0.69", "tracing", + "uplc-turbo", ] [[package]] @@ -2822,6 +2929,15 @@ dependencies = [ "prost", ] +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + [[package]] name = "ptr" version = "0.2.3" @@ -3062,6 +3178,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + [[package]] name = "regex-automata" version = "0.4.9" @@ -3079,6 +3206,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -3190,6 +3323,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rug" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3339,6 +3484,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand 0.8.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -3717,6 +3882,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3886,6 +4064,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.41" @@ -4315,6 +4502,23 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uplc-turbo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214465664561ad67daf411e1d575a67cf25107369191f26c16243ac98073c0a8" +dependencies = [ + "blst", + "bumpalo", + "chumsky", + "cryptoxide", + "minicbor 0.25.1", + "once_cell", + "rug", + "secp256k1", + "thiserror 1.0.69", +] + [[package]] name = "ureq" version = "2.12.1" @@ -4580,23 +4784,10 @@ dependencies = [ "windows-implement 0.58.0", "windows-interface 0.58.0", "windows-result 0.2.0", - "windows-strings 0.1.0", + "windows-strings", "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" -dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link", - "windows-result 0.3.2", - "windows-strings 0.4.0", -] - [[package]] name = "windows-implement" version = "0.57.0" @@ -4619,17 +4810,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-interface" version = "0.57.0" @@ -4652,17 +4832,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-link" version = "0.1.1" @@ -4676,7 +4845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result 0.2.0", - "windows-strings 0.1.0", + "windows-strings", "windows-targets 0.52.6", ] @@ -4698,15 +4867,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.1.0" @@ -4717,15 +4877,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-strings" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5010,6 +5161,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index 3fe039ae..ae28bb89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ sentry = "0.36.0" blake2 = "0.10.6" base64 = "0.22.1" num_cpus = "1" -pallas = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } +pallas = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2", features = ["phase2"] } pallas-network = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } pallas-crypto = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } pallas-traverse = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } @@ -39,7 +39,7 @@ pallas-codec = { git = "https://github.com/blockfrost/pallas.git", tag = "blockf pallas-addresses = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } pallas-primitives = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } pallas-hardano = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } -pallas-validate = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2" } +pallas-validate = { git = "https://github.com/blockfrost/pallas.git", tag = "blockfrost-platform-0.0.3-alpha2", features = ["phase2"] } reqwest = "0.12.12" hex = "0.4.3" metrics = { version = "0.24.1", default-features = false } diff --git a/src/api/tx/submit.rs b/src/api/tx/submit.rs index 5122bf1a..5d27ad5b 100644 --- a/src/api/tx/submit.rs +++ b/src/api/tx/submit.rs @@ -1,4 +1,7 @@ -use crate::{BlockfrostError, NodePool, common::validate_content_type}; +use crate::{ + BlockfrostError, NodePool, + common::{binary_or_hex_heuristic, validate_content_type}, +}; use axum::{Extension, Json, http::HeaderMap, response::IntoResponse}; use metrics::counter; @@ -39,44 +42,3 @@ pub async fn route( Ok((response_headers, Json(response_body))) } - -/// This function allows us to take both hex-encoded and raw bytes. It has -/// to be a heuristic: if there are input bytes that are not `[0-9a-f]`, -/// then it must be a binary string. Otherwise, we assume it’s hex encoded. -/// -/// **Note**: there is a small probability that the user gave us a binary -/// string that only _looked_ like a hex-encoded one, but it’s rare enough -/// to ignore it. -pub fn binary_or_hex_heuristic(xs: &[u8]) -> Vec { - let even_length = xs.len() % 2 == 0; - let contains_non_hex = xs.iter().any(|&x| !x.is_ascii_hexdigit()); - - if !even_length || contains_non_hex { - xs.to_vec() - } else { - hex::decode(xs).unwrap_or_else(|_| unreachable!()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn proptest_binary_or_hex_heuristic( - binary in prop::collection::vec(any::(), 0..=128) - .prop_filter("exclude values made up only of hex digits", |xs| { - let contains_non_hex = xs.iter().any(|&x| !x.is_ascii_hexdigit()); - contains_non_hex - }) - ) { - let hex_string = hex::encode(&binary); - assert_eq!( - binary_or_hex_heuristic(hex_string.as_bytes()), - binary_or_hex_heuristic(&binary) - ) - } - } -} diff --git a/src/api/utils/txs/evaluate.rs b/src/api/utils/txs/evaluate.rs index b1ee3085..dbfda6ba 100644 --- a/src/api/utils/txs/evaluate.rs +++ b/src/api/utils/txs/evaluate.rs @@ -1,2 +1,3 @@ +pub mod model; pub mod root; pub mod utxos; diff --git a/src/api/utils/txs/evaluate/model.rs b/src/api/utils/txs/evaluate/model.rs new file mode 100644 index 00000000..cdf64e2a --- /dev/null +++ b/src/api/utils/txs/evaluate/model.rs @@ -0,0 +1,306 @@ +use std::collections::HashMap; + +use pallas_primitives::{ + Bytes, ExUnits, KeepRaw, PlutusScript, + conway::{NativeScript, RedeemerTag, ScriptRef}, +}; +use pallas_validate::phase2::EvalReport; +use pallas_validate::phase2::tx::TxEvalResult; +use serde::{Deserialize, Serialize, ser::SerializeMap}; + +// JSON request +#[derive(Deserialize)] +pub struct TxEvaluationRequest { + pub cbor: String, // @todo can be base16 or base64 CBOR + #[serde(rename = "additionalUtxoSet")] + pub additional_utxo_set: Option, +} + +pub type AdditionalUtxoSet = Vec<(TxIn, TxOut)>; +#[derive(Deserialize)] +pub struct TxIn { + #[serde(rename = "txId")] + pub tx_id: String, + pub index: u64, +} +#[derive(Deserialize)] +pub struct TxOut { + pub address: String, + pub value: Value, + #[serde(rename = "datumHash")] + pub datum_hash: Option, + pub datum: Option, + pub script: Option