diff --git a/Cargo.lock b/Cargo.lock index cf4d9322..c73a1cc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "sha2 0.10.9", "tempfile", "thiserror 2.0.17", @@ -219,7 +219,7 @@ dependencies = [ "caryatid_sdk", "config", "serde", - "serde_with 3.15.1", + "serde_with 3.16.0", "tokio", "tracing", ] @@ -267,7 +267,7 @@ dependencies = [ "hex", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "tokio", "tracing", "tracing-subscriber", @@ -384,7 +384,7 @@ dependencies = [ "serde", "serde_cbor", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "tokio", "tracing", ] @@ -456,7 +456,7 @@ dependencies = [ "pallas 0.33.0", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "tokio", "tracing", ] @@ -488,6 +488,10 @@ dependencies = [ "futures", "hex", "pallas 0.33.0", + "serde", + "serde_json", + "test-case", + "tokio", "tracing", ] @@ -607,7 +611,7 @@ dependencies = [ "opentelemetry_sdk", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "tikv-jemallocator", "tokio", "tracing", @@ -801,22 +805,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -867,7 +871,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "synstructure", ] @@ -879,7 +883,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -906,9 +910,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2" dependencies = [ "compression-codecs", "compression-core", @@ -1018,7 +1022,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1035,7 +1039,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1063,9 +1067,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "5932a7d9d28b0d2ea34c6b3779d35e3dd6f6345317c34e73438c4f1f29144151" dependencies = [ "aws-lc-sys", "zeroize", @@ -1073,9 +1077,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.3" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +checksum = "1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc" dependencies = [ "bindgen", "cc", @@ -1250,7 +1254,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1309,7 +1313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6cbbb8f56245b5a479b30a62cdc86d26e2f35c2b9f594bc4671654b03851380" dependencies = [ "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1416,7 +1420,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1621,9 +1625,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -1663,7 +1667,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1769,7 +1773,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1813,9 +1817,9 @@ checksum = "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7" [[package]] name = "compression-codecs" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b" dependencies = [ "brotli", "compression-core", @@ -1827,9 +1831,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" +checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582" [[package]] name = "concurrent-queue" @@ -1842,9 +1846,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.18" +version = "0.15.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" dependencies = [ "async-trait", "convert_case", @@ -2003,9 +2007,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -2090,7 +2094,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2124,7 +2128,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2138,7 +2142,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2149,7 +2153,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2160,7 +2164,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2256,7 +2260,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2306,7 +2310,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2401,7 +2405,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2505,9 +2509,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixed" @@ -2699,7 +2703,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2754,9 +2758,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -3052,9 +3056,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -3080,7 +3084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "rustls", "rustls-pki-types", @@ -3096,7 +3100,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -3124,7 +3128,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -3134,9 +3138,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", @@ -3145,7 +3149,7 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.7.0", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", @@ -3515,7 +3519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3658,14 +3662,14 @@ checksum = "bd2209fff77f705b00c737016a48e73733d7fbccb8b007194db148f03561fb70" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] name = "minicbor-serde" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546cc904f35809921fa57016a84c97e68d9d27c012e87b9dadc28c233705f783" +checksum = "80047f75e28e3b38f6ab2ec3c2c7669f6b411fa6f8424e1a90a3fd784b19a3f4" dependencies = [ "minicbor 2.1.3", "serde", @@ -3787,7 +3791,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", "sha2 0.10.9", "slog", "strum", @@ -3998,7 +4002,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4275,7 +4279,7 @@ dependencies = [ "pallas-primitives 0.32.1", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", ] [[package]] @@ -4293,7 +4297,7 @@ dependencies = [ "pallas-primitives 0.33.0", "serde", "serde_json", - "serde_with 3.15.1", + "serde_with 3.16.0", ] [[package]] @@ -4618,7 +4622,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4743,7 +4747,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4783,7 +4787,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4900,7 +4904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4947,7 +4951,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.109", + "syn 2.0.110", "tempfile", ] @@ -4961,7 +4965,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5254,7 +5258,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5352,7 +5356,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-rustls", "hyper-tls 0.6.0", "hyper-util", @@ -5429,14 +5433,16 @@ dependencies = [ [[package]] name = "ron" -version = "0.8.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64 0.21.7", "bitflags 2.10.0", + "once_cell", "serde", "serde_derive", + "typeid", + "unicode-ident", ] [[package]] @@ -5813,7 +5819,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5879,9 +5885,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "base64 0.22.1", "chrono", @@ -5892,7 +5898,7 @@ dependencies = [ "schemars 1.1.0", "serde_core", "serde_json", - "serde_with_macros 3.15.1", + "serde_with_macros 3.16.0", "time", ] @@ -5905,19 +5911,19 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6127,7 +6133,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6149,9 +6155,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -6181,7 +6187,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6270,6 +6276,39 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", + "test-case-core", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -6305,7 +6344,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6316,7 +6355,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6471,7 +6510,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6594,7 +6633,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -6694,7 +6733,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6793,7 +6832,7 @@ checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7030,7 +7069,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -7124,9 +7163,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -7137,7 +7176,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7148,15 +7187,9 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -7165,22 +7198,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-result" -version = "0.3.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -7189,16 +7213,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -7207,7 +7222,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -7243,7 +7258,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -7283,7 +7298,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -7463,7 +7478,7 @@ dependencies = [ "futures", "http 1.3.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "log", "once_cell", @@ -7569,7 +7584,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "synstructure", ] @@ -7590,7 +7605,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7610,7 +7625,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "synstructure", ] @@ -7631,7 +7646,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7664,7 +7679,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs index 150609d3..a4869013 100644 --- a/codec/src/map_parameters.rs +++ b/codec/src/map_parameters.rs @@ -13,12 +13,14 @@ use pallas::ledger::{ }; use acropolis_common::hash::Hash; +use acropolis_common::validation::ValidationError; use acropolis_common::{ protocol_params::{Nonce, NonceVariant, ProtocolVersion}, rational_number::RationalNumber, *, }; use pallas_primitives::conway::PseudoScript; +use pallas_traverse::{MultiEraInput, MultiEraTx}; use std::{ collections::{HashMap, HashSet}, net::{Ipv4Addr, Ipv6Addr}, @@ -265,16 +267,14 @@ pub fn to_pool_reg( reward_account: if force_reward_network_id { StakeAddress::new( StakeAddress::from_binary(reward_account)?.credential, - network_id.clone(), + network_id, ) } else { StakeAddress::from_binary(reward_account)? }, pool_owners: pool_owners .iter() - .map(|v| { - StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id.clone()) - }) + .map(|v| StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id)) .collect(), relays: relays.iter().map(map_relay).collect(), pool_metadata: match pool_metadata { @@ -379,7 +379,7 @@ pub fn map_certificate( InstantaneousRewardTarget::StakeAddresses( creds .iter() - .map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v)) + .map(|(sc, v)| (map_stake_address(sc, network_id), *v)) .collect(), ) } @@ -990,6 +990,111 @@ pub fn map_all_governance_voting_procedures( Ok(procs) } +// pub struct TransactionRefInfo {} + +// pub fn map_transaction_refs( +// inputs: &Vec, +// outputs: &Vec<(usize, MultiEraOutput)>, +// ) -> (Vec, Vec) { +// let mut ref_inps = Vec::new(); +// for input in inputs { +// // MultiEraInput +// let oref = input.output_ref(); +// let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16); +// ref_inps.push(tx_ref); +// } + +// let mut ref_outs = Vec::new(); +// for (index, output) in outputs {} + +// (ref_inps, ref_outs) +// } + +pub fn map_transaction_inputs(inputs: &Vec) -> Vec { + let mut parsed_inputs = Vec::new(); + for input in inputs { + // MultiEraInput + let oref = input.output_ref(); + let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16); + + parsed_inputs.push(tx_ref); + } + + parsed_inputs +} + +pub fn map_transaction_inputs_outputs( + block_number: u32, + tx_index: u16, + tx: &MultiEraTx, +) -> ( + Vec, + Vec<(TxOutRef, TxOutput)>, + Vec, +) { + let mut parsed_inputs = Vec::new(); + let mut parsed_outputs = Vec::new(); + let mut errors = Vec::new(); + + let Ok(tx_hash) = tx.hash().to_vec().try_into() else { + errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Tx has incorrect hash length ({:?})", tx.hash().to_vec()), + )); + return (parsed_inputs, parsed_outputs, errors); + }; + + let inputs = tx.consumes(); + let outputs = tx.produces(); + + for input in inputs { + let tx_ref = TxOutRef::new( + TxHash::from(**input.output_ref().hash()), + input.output_ref().index() as u16, + ); + parsed_inputs.push(tx_ref); + } + + for (index, output) in outputs { + let tx_out_ref = TxOutRef { + tx_hash, + output_index: index as u16, + }; + + let utxo_id = UTxOIdentifier::new(block_number, tx_index, tx_out_ref.output_index); + + match output.address() { + Ok(pallas_address) => match map_address(&pallas_address) { + Ok(address) => { + // Add TxOutput to utxo_deltas + parsed_outputs.push(( + tx_out_ref, + TxOutput { + utxo_identifier: utxo_id, + address, + value: map_value(&output.value()), + datum: map_datum(&output.datum()), + reference_script: map_reference_script(&output.script_ref()), + }, + )); + } + Err(e) => { + errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Output {index} has been ignored: {e}"), + )); + } + }, + Err(e) => errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Can't parse output {index} in tx: {e}"), + )), + } + } + + (parsed_inputs, parsed_outputs, errors) +} + pub fn map_value(pallas_value: &MultiEraValue) -> Value { let lovelace = pallas_value.coin(); let pallas_assets = pallas_value.assets(); diff --git a/common/src/address.rs b/common/src/address.rs index f770d2c1..0d376cce 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -776,7 +776,10 @@ mod tests { }); let text = address.to_string().unwrap(); - assert_eq!(text, "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x"); + assert_eq!( + text, + "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x" + ); let unpacked = Address::from_string(&text).unwrap(); assert_eq!(address, unpacked); @@ -791,7 +794,10 @@ mod tests { }); let text = address.to_string().unwrap(); - assert_eq!(text, "addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh"); + assert_eq!( + text, + "addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh" + ); let unpacked = Address::from_string(&text).unwrap(); assert_eq!(address, unpacked); @@ -806,7 +812,10 @@ mod tests { }); let text = address.to_string().unwrap(); - assert_eq!(text, "addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve"); + assert_eq!( + text, + "addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve" + ); let unpacked = Address::from_string(&text).unwrap(); assert_eq!(address, unpacked); @@ -821,7 +830,10 @@ mod tests { }); let text = address.to_string().unwrap(); - assert_eq!(text, "addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g"); + assert_eq!( + text, + "addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g" + ); let unpacked = Address::from_string(&text).unwrap(); assert_eq!(address, unpacked); @@ -935,8 +947,14 @@ mod tests { #[test] fn shelley_to_stake_address_string_mainnet() { - let normal_address = ShelleyAddress::from_string("addr1q82peck5fynytkgjsp9vnpul59zswsd4jqnzafd0mfzykma625r684xsx574ltpznecr9cnc7n9e2hfq9lyart3h5hpszffds5").expect("valid normal address"); - let script_address = ShelleyAddress::from_string("addr1zx0whlxaw4ksygvuljw8jxqlw906tlql06ern0gtvvzhh0c6409492020k6xml8uvwn34wrexagjh5fsk5xk96jyxk2qhlj6gf").expect("valid script address"); + let normal_address = ShelleyAddress::from_string( + "addr1q82peck5fynytkgjsp9vnpul59zswsd4jqnzafd0mfzykma625r684xsx574ltpznecr9cnc7n9e2hfq9lyart3h5hpszffds5", + ) + .expect("valid normal address"); + let script_address = ShelleyAddress::from_string( + "addr1zx0whlxaw4ksygvuljw8jxqlw906tlql06ern0gtvvzhh0c6409492020k6xml8uvwn34wrexagjh5fsk5xk96jyxk2qhlj6gf", + ) + .expect("valid script address"); let normal_stake_address = normal_address .stake_address_string() diff --git a/common/src/protocol_params.rs b/common/src/protocol_params.rs index cfc07d10..a5194def 100644 --- a/common/src/protocol_params.rs +++ b/common/src/protocol_params.rs @@ -21,6 +21,17 @@ pub struct ProtocolParams { pub conway: Option, } +impl ProtocolParams { + /// Calculate Transaction's Mininum required fee for shelley Era + /// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Tx.hs#L254 + pub fn shelley_min_fee(&self, tx_bytes: u32) -> Result { + self.shelley + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Shelley params are not set")) + .map(|shelley_params| shelley_params.min_fee(tx_bytes)) + } +} + // // Byron protocol parameters // @@ -134,6 +145,13 @@ pub struct ShelleyParams { pub gen_delegs: HashMap, } +impl ShelleyParams { + pub fn min_fee(&self, tx_bytes: u32) -> u64 { + (tx_bytes as u64 * self.protocol_params.minfee_a as u64) + + (self.protocol_params.minfee_b as u64) + } +} + #[serde_as] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] @@ -190,7 +208,7 @@ impl From<&ShelleyParams> for PraosParams { epoch_length: params.epoch_length, max_kes_evolutions: params.max_kes_evolutions, max_lovelace_supply: params.max_lovelace_supply, - network_id: params.network_id.clone(), + network_id: params.network_id, slot_length: params.slot_length, slots_per_kes_period: params.slots_per_kes_period, diff --git a/common/src/types.rs b/common/src/types.rs index e5f35740..3eaba0b5 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -30,6 +30,7 @@ use std::{ /// Network identifier #[derive( Debug, + Copy, Clone, Default, PartialEq, @@ -61,6 +62,19 @@ impl From for NetworkId { } } +impl Display for NetworkId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + NetworkId::Mainnet => "mainnet", + NetworkId::Testnet => "testnet", + } + ) + } +} + /// Protocol era #[derive( Debug, @@ -323,7 +337,14 @@ impl AssetName { } #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Encode, minicbor::Decode, + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + minicbor::Encode, + minicbor::Decode, + PartialEq, + Eq, )] pub struct NativeAsset { #[n(0)] @@ -343,7 +364,7 @@ pub struct NativeAssetDelta { } /// Datum (inline or hash) -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub enum Datum { Hash(Vec), Inline(Vec), @@ -359,7 +380,7 @@ pub enum ReferenceScript { } /// Value (lovelace + multiasset) -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)] pub struct Value { pub lovelace: u64, pub assets: NativeAssets, @@ -562,7 +583,7 @@ pub struct UTXOValue { } /// Transaction output (UTXO) -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct TxOutput { /// Identifier for this UTxO pub utxo_identifier: UTxOIdentifier, @@ -721,6 +742,12 @@ impl TxOutRef { } } +impl Display for TxOutRef { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}#{}", self.tx_hash, self.output_index) + } +} + /// Slot pub type Slot = u64; diff --git a/common/src/validation.rs b/common/src/validation.rs index d92c06bd..63363add 100644 --- a/common/src/validation.rs +++ b/common/src/validation.rs @@ -7,7 +7,108 @@ use std::array::TryFromSliceError; use thiserror::Error; -use crate::{protocol_params::Nonce, GenesisKeyhash, PoolId, Slot, VrfKeyHash}; +use crate::{ + protocol_params::Nonce, Address, Era, GenesisKeyhash, Lovelace, NetworkId, PoolId, Slot, + StakeAddress, TxOutRef, Value, VrfKeyHash, +}; + +/// Transaction Validation Error +/// +/// Shelley Era Errors: +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L343 +/// +/// Allegra Era Errors: +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/allegra/impl/src/Cardano/Ledger/Allegra/Rules/Utxo.hs#L160 +/// +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Error, PartialEq, Eq)] +pub enum TransactionValidationError { + /// **Cause**: Raw Transaction CBOR is invalid + #[error("CBOR Decoding error: {0}")] + CborDecodeError(String), + + /// **Cause**: Transaction is not in correct form. + #[error("Malformed Transaction: era={era}, reason={reason}")] + MalformedTransaction { era: Era, reason: String }, + + /// **Cause**: UTXO validation error + #[error("{0}")] + UTxOValidationError(#[from] UTxOValidationError), + + /// **Cause:** Other errors (e.g. Invalid shelley params) + #[error("{0}")] + Other(String), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Error, PartialEq, Eq)] +pub enum UTxOValidationError { + /// ------------ Shelley Era Errors ------------ + /// **Cause:** The UTXO has expired + #[error("Expired UTXO: ttl={ttl}, current_slot={current_slot}")] + ExpiredUTxO { ttl: Slot, current_slot: Slot }, + + /// **Cause:** The input set is empty. (genesis transactions are exceptions) + #[error("Input Set Empty UTXO")] + InputSetEmptyUTxO, + + /// **Cause:** The fee is too small. + #[error("Fee is too small: supplied={supplied}, required={required}")] + FeeTooSmallUTxO { + supplied: Lovelace, + required: Lovelace, + }, + + /// **Cause:** Some of transaction inputs are not in current UTxOs set. + #[error("Bad inputs: bad_input={bad_input}, bad_input_index={bad_input_index}")] + BadInputsUTxO { + bad_input: TxOutRef, + bad_input_index: usize, + }, + + /// **Cause:** Some of transaction outputs are on a different network than the expected one. + #[error( + "Wrong network: expected={expected}, wrong_address={}, output_index={output_index}", + wrong_address.to_string().unwrap_or("Invalid address".to_string()), + )] + WrongNetwork { + expected: NetworkId, + wrong_address: Address, + output_index: usize, + }, + + /// **Cause:** Some of withdrawal accounts are on a different network than the expected one. + #[error( + "Wrong network withdrawal: expected={expected}, wrong_account={}, withdrawal_index={withdrawal_index}", + wrong_account.to_string().unwrap_or("Invalid stake address".to_string()), + )] + WrongNetworkWithdrawal { + expected: NetworkId, + wrong_account: StakeAddress, + withdrawal_index: usize, + }, + + /// **Cause:** The value of the UTXO is not conserved. + /// Consumed = inputs + withdrawals + refunds, Produced = outputs + fees + deposits + #[error("Value not conserved: consumed={consumed:?}, produced={produced:?}]")] + ValueNotConservedUTxO { consumed: Value, produced: Value }, + + /// **Cause:** Some of the outputs don't have minimum required lovelace + #[error( + "Output too small UTxO: output_index={output_index}, lovelace={lovelace}, required_lovelace={required_lovelace}" + )] + OutputTooSmallUTxO { + output_index: usize, + lovelace: Lovelace, + required_lovelace: Lovelace, + }, + + /// **Cause:** The transaction size is too big. + #[error("Max tx size: supplied={supplied}, max={max}")] + MaxTxSizeUTxO { supplied: u32, max: u32 }, + + /// **Cause:** Malformed UTxO + #[error("Malformed UTxO: era={era}, reason={reason}")] + MalformedUTxO { era: Era, reason: String }, +} /// Validation error #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Error)] @@ -18,6 +119,18 @@ pub enum ValidationError { #[error("KES failure: {0}")] BadKES(#[from] KesValidationError), + #[error("Invalid Transaction: tx-index={tx_index}, error={error}")] + BadTransaction { + tx_index: u16, + error: TransactionValidationError, + }, + + #[error("CBOR Decoding error")] + CborDecodeError(usize, String), + + #[error("Malformed transaction")] + MalformedTransaction(u16, String), + #[error("Doubly spent UTXO: {0}")] DoubleSpendUTXO(String), } diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index dabf5648..e875b5a6 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -238,13 +238,15 @@ impl Verifier { } Both(expected, actual) => { if expected.amount != actual.amount { - error!("Different reward: SPO {} account {} {:?} expected {}, actual {} ({})", - expected_spo.0, - expected.account, - expected.rtype, - expected.amount, - actual.amount, - actual.amount as i64-expected.amount as i64); + error!( + "Different reward: SPO {} account {} {:?} expected {}, actual {} ({})", + expected_spo.0, + expected.account, + expected.rtype, + expected.amount, + actual.amount, + actual.amount as i64 - expected.amount as i64 + ); errors += 1; } else { debug!( diff --git a/modules/address_state/src/address_state.rs b/modules/address_state/src/address_state.rs index ec746b0e..e72b34af 100644 --- a/modules/address_state/src/address_state.rs +++ b/modules/address_state/src/address_state.rs @@ -207,26 +207,23 @@ impl AddressState { let state = state_mutex.lock().await; let response = match query { - AddressStateQuery::GetAddressUTxOs { address } => { - match state.get_address_utxos(address).await { - Ok(Some(utxos)) => AddressStateQueryResponse::AddressUTxOs(utxos), - Ok(None) => match address.to_string() { - Ok(addr_str) => { - AddressStateQueryResponse::Error(QueryError::not_found( - format!("Address {} not found", addr_str), - )) - } - Err(e) => { - AddressStateQueryResponse::Error(QueryError::internal_error( - format!("Could not convert address to string: {}", e), - )) - } - }, + AddressStateQuery::GetAddressUTxOs { address } => match state + .get_address_utxos(address) + .await + { + Ok(Some(utxos)) => AddressStateQueryResponse::AddressUTxOs(utxos), + Ok(None) => match address.to_string() { + Ok(addr_str) => AddressStateQueryResponse::Error( + QueryError::not_found(format!("Address {} not found", addr_str)), + ), Err(e) => AddressStateQueryResponse::Error(QueryError::internal_error( - e.to_string(), + format!("Could not convert address to string: {}", e), )), - } - } + }, + Err(e) => AddressStateQueryResponse::Error(QueryError::internal_error( + e.to_string(), + )), + }, AddressStateQuery::GetAddressTransactions { address } => { match state.get_address_transactions(address).await { Ok(Some(txs)) => AddressStateQueryResponse::AddressTransactions(txs), diff --git a/modules/address_state/src/state.rs b/modules/address_state/src/state.rs index 350f4e13..abfbcc8e 100644 --- a/modules/address_state/src/state.rs +++ b/modules/address_state/src/state.rs @@ -237,7 +237,10 @@ mod tests { use tempfile::tempdir; fn dummy_address() -> Address { - Address::from_string("DdzFFzCqrht7fNAHwdou7iXPJ5NZrssAH53yoRMUtF9t6momHH52EAxM5KmqDwhrjT7QsHjbMPJUBywmzAgmF4hj2h9eKj4U6Ahandyy").unwrap() + Address::from_string( + "DdzFFzCqrht7fNAHwdou7iXPJ5NZrssAH53yoRMUtF9t6momHH52EAxM5KmqDwhrjT7QsHjbMPJUBywmzAgmF4hj2h9eKj4U6Ahandyy", + ) + .unwrap() } fn test_config() -> AddressStorageConfig { diff --git a/modules/block_kes_validator/src/ouroboros/kes.rs b/modules/block_kes_validator/src/ouroboros/kes.rs index 0ecbfb2d..802a2c66 100644 --- a/modules/block_kes_validator/src/ouroboros/kes.rs +++ b/modules/block_kes_validator/src/ouroboros/kes.rs @@ -68,7 +68,10 @@ impl Signature { impl From<&[u8; Self::SIZE]> for Signature { fn from(bytes: &[u8; Self::SIZE]) -> Self { Signature(Sum6KesSig::from_bytes(bytes).unwrap_or_else(|e| { - unreachable!("Impossible! Failed to create a KES signature from a slice ({}) of known size: {e:?}", hex::encode(bytes)) + unreachable!( + "Impossible! Failed to create a KES signature from a slice ({}) of known size: {e:?}", + hex::encode(bytes) + ) })) } } diff --git a/modules/chain_store/src/chain_store.rs b/modules/chain_store/src/chain_store.rs index d43c46e2..09547d23 100644 --- a/modules/chain_store/src/chain_store.rs +++ b/modules/chain_store/src/chain_store.rs @@ -113,7 +113,6 @@ impl ChainStore { let query_store = store.clone(); context.handle(&txs_queries_topic, move |req| { let query_store = query_store.clone(); - let network_id = network_id.clone(); async move { let Message::StateQuery(StateQuery::Transactions(query)) = req.as_ref() else { return Arc::new(Message::StateQueryResponse( @@ -893,14 +892,14 @@ impl ChainStore { alonzo::Certificate::StakeRegistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: true, }); } alonzo::Certificate::StakeDeregistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: false, }); } @@ -910,28 +909,28 @@ impl ChainStore { conway::Certificate::StakeRegistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: true, }); } conway::Certificate::StakeDeregistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: false, }); } conway::Certificate::StakeRegDeleg(cred, _, _) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: true, }); } conway::Certificate::StakeVoteRegDeleg(cred, _, _, _) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), registration: true, }); } @@ -961,7 +960,7 @@ impl ChainStore { { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), pool_id: to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); @@ -971,7 +970,7 @@ impl ChainStore { conway::Certificate::StakeDelegation(cred, pool_key_hash) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), pool_id: to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); @@ -979,7 +978,7 @@ impl ChainStore { conway::Certificate::StakeRegDeleg(cred, pool_key_hash, _) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), pool_id: to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); @@ -987,7 +986,7 @@ impl ChainStore { conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, _, _) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: map_stake_address(cred, network_id), pool_id: to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); @@ -1041,7 +1040,7 @@ impl ChainStore { InstantaneousRewardSource::Treasury } }, - address: map_stake_address(&cred, network_id.clone()), + address: map_stake_address(&cred, network_id), amount: amount as u64, }); } @@ -1093,7 +1092,7 @@ impl ChainStore { pool_owners, relays, pool_metadata, - network_id.clone(), + network_id, false, )?, // Pool registration/updates become active after 2 epochs @@ -1126,7 +1125,7 @@ impl ChainStore { pool_owners, relays, pool_metadata, - network_id.clone(), + network_id, false, )?, // Pool registration/updates become active after 2 epochs diff --git a/modules/drep_state/src/drep_state.rs b/modules/drep_state/src/drep_state.rs index b1de308d..98b87130 100644 --- a/modules/drep_state/src/drep_state.rs +++ b/modules/drep_state/src/drep_state.rs @@ -332,75 +332,65 @@ impl DRepState { ), } } - GovernanceStateQuery::GetDRepDelegators { drep_credential } => { - match locked.current() { - Some(state) => match state.get_drep_delegators(drep_credential) { - Ok(Some(delegators)) => { - GovernanceStateQueryResponse::DRepDelegators( - DRepDelegatorAddresses { - addresses: delegators.clone(), - }, - ) - } - Ok(None) => GovernanceStateQueryResponse::Error( - QueryError::not_found(format!( - "DRep delegators for {:?} not found", - drep_credential - )), - ), - Err(msg) => GovernanceStateQueryResponse::Error( - QueryError::internal_error(msg), - ), - }, - None => GovernanceStateQueryResponse::Error( - QueryError::internal_error("No current state"), + GovernanceStateQuery::GetDRepDelegators { drep_credential } => match locked + .current() + { + Some(state) => match state.get_drep_delegators(drep_credential) { + Ok(Some(delegators)) => GovernanceStateQueryResponse::DRepDelegators( + DRepDelegatorAddresses { + addresses: delegators.clone(), + }, ), - } - } - GovernanceStateQuery::GetDRepMetadata { drep_credential } => { - match locked.current() { - Some(state) => match state.get_drep_anchor(drep_credential) { - Ok(Some(anchor)) => GovernanceStateQueryResponse::DRepMetadata( - Some(Some(anchor.clone())), - ), - Ok(None) => GovernanceStateQueryResponse::Error( - QueryError::not_found(format!( - "DRep metadata for {:?} not found", - drep_credential - )), - ), - Err(msg) => GovernanceStateQueryResponse::Error( - QueryError::internal_error(msg), - ), - }, - None => GovernanceStateQueryResponse::Error( - QueryError::internal_error("No current state"), - ), - } - } + Ok(None) => GovernanceStateQueryResponse::Error(QueryError::not_found( + format!("DRep delegators for {:?} not found", drep_credential), + )), + Err(msg) => { + GovernanceStateQueryResponse::Error(QueryError::internal_error(msg)) + } + }, + None => GovernanceStateQueryResponse::Error(QueryError::internal_error( + "No current state", + )), + }, + GovernanceStateQuery::GetDRepMetadata { drep_credential } => match locked + .current() + { + Some(state) => match state.get_drep_anchor(drep_credential) { + Ok(Some(anchor)) => GovernanceStateQueryResponse::DRepMetadata(Some( + Some(anchor.clone()), + )), + Ok(None) => GovernanceStateQueryResponse::Error(QueryError::not_found( + format!("DRep metadata for {:?} not found", drep_credential), + )), + Err(msg) => { + GovernanceStateQueryResponse::Error(QueryError::internal_error(msg)) + } + }, + None => GovernanceStateQueryResponse::Error(QueryError::internal_error( + "No current state", + )), + }, - GovernanceStateQuery::GetDRepUpdates { drep_credential } => { - match locked.current() { - Some(state) => match state.get_drep_updates(drep_credential) { - Ok(Some(updates)) => { - GovernanceStateQueryResponse::DRepUpdates(DRepUpdates { - updates: updates.to_vec(), - }) - } - Ok(None) => { - GovernanceStateQueryResponse::Error(QueryError::not_found( - format!("DRep updates for {:?} not found", drep_credential), - )) - } - Err(msg) => GovernanceStateQueryResponse::Error( - QueryError::internal_error(msg), - ), - }, - None => GovernanceStateQueryResponse::Error( - QueryError::internal_error("No current state"), - ), - } - } + GovernanceStateQuery::GetDRepUpdates { drep_credential } => match locked + .current() + { + Some(state) => match state.get_drep_updates(drep_credential) { + Ok(Some(updates)) => { + GovernanceStateQueryResponse::DRepUpdates(DRepUpdates { + updates: updates.to_vec(), + }) + } + Ok(None) => GovernanceStateQueryResponse::Error(QueryError::not_found( + format!("DRep updates for {:?} not found", drep_credential), + )), + Err(msg) => { + GovernanceStateQueryResponse::Error(QueryError::internal_error(msg)) + } + }, + None => GovernanceStateQueryResponse::Error(QueryError::internal_error( + "No current state", + )), + }, GovernanceStateQuery::GetDRepVotes { drep_credential } => { match locked.current() { Some(state) => match state.get_drep_votes(drep_credential) { diff --git a/modules/governance_state/src/conway_voting.rs b/modules/governance_state/src/conway_voting.rs index 41f2064f..cf546281 100644 --- a/modules/governance_state/src/conway_voting.rs +++ b/modules/governance_state/src/conway_voting.rs @@ -154,8 +154,13 @@ impl ConwayVoting { // Re-voting is allowed; new vote must be treated as the proper one, // older is to be discarded. if tracing::enabled!(tracing::Level::DEBUG) { - debug!("Governance vote by {} for {} already registered! New: {:?}, old: {:?} from {}", - voter, action_id, procedure, prev_vote, prev_trans.encode_hex::() + debug!( + "Governance vote by {} for {} already registered! New: {:?}, old: {:?} from {}", + voter, + action_id, + procedure, + prev_vote, + prev_trans.encode_hex::() ); } } diff --git a/modules/governance_state/src/governance_state.rs b/modules/governance_state/src/governance_state.rs index 80d36fc5..4132c2be 100644 --- a/modules/governance_state/src/governance_state.rs +++ b/modules/governance_state/src/governance_state.rs @@ -195,18 +195,16 @@ impl GovernanceState { GovernanceStateQueryResponse::ProposalsList(ProposalsList { proposals }) } - GovernanceStateQuery::GetProposalInfo { proposal } => { - match locked.get_proposal(proposal) { - Some(proc) => { - GovernanceStateQueryResponse::ProposalInfo(ProposalInfo { - procedure: proc.clone(), - }) - } - None => GovernanceStateQueryResponse::Error(QueryError::not_found( - format!("Proposal {} not found", proposal), - )), - } - } + GovernanceStateQuery::GetProposalInfo { proposal } => match locked + .get_proposal(proposal) + { + Some(proc) => GovernanceStateQueryResponse::ProposalInfo(ProposalInfo { + procedure: proc.clone(), + }), + None => GovernanceStateQueryResponse::Error(QueryError::not_found( + format!("Proposal {} not found", proposal), + )), + }, GovernanceStateQuery::GetProposalVotes { proposal } => { match locked.get_proposal_votes(proposal) { Ok(votes) => { @@ -249,9 +247,7 @@ impl GovernanceState { if blk_g.new_epoch { let (blk_p, params) = Self::read_parameters(&mut protocol_s).await?; if blk_g != blk_p { - error!( - "Governance {blk_g:?} and protocol parameters {blk_p:?} are out of sync" - ); + error!("Governance {blk_g:?} and protocol parameters {blk_p:?} are out of sync"); } { @@ -267,16 +263,11 @@ impl GovernanceState { let (blk_spo, d_spo) = Self::read_spo(&mut spo_s).await?; if blk_g != blk_spo { - error!( - "Governance {blk_g:?} and SPO distribution {blk_spo:?} are out of sync" - ); + error!("Governance {blk_g:?} and SPO distribution {blk_spo:?} are out of sync"); } if blk_spo.epoch != d_spo.epoch + 1 { - error!( - "SPO distibution {blk_spo:?} != SPO epoch + 1 ({})", - d_spo.epoch - ); + error!("SPO distibution {blk_spo:?} != SPO epoch + 1 ({})", d_spo.epoch); } state.lock().await.handle_drep_stake(&d_drep, &d_spo).await? @@ -287,7 +278,9 @@ impl GovernanceState { } } Ok::<(), anyhow::Error>(()) - }.instrument(span).await?; + } + .instrument(span) + .await?; } } diff --git a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs index 1a1a91e1..1d74304f 100644 --- a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs +++ b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs @@ -160,9 +160,7 @@ impl MithrilSnapshotFetcher { true } } else { - info!( - "SKIP DOWNLOAD: Snapshot is not expired by download max age: {download_max_age} hours" - ); + info!("SKIP DOWNLOAD: Snapshot is not expired by download max age: {download_max_age} hours"); true } } @@ -439,9 +437,7 @@ impl MithrilSnapshotFetcher { /// Async helper to prompt user for pause behavior async fn prompt_pause(description: String) -> bool { - info!( - "Paused at {description}. Press [Enter] to step to to the next, or [c + Enter] to continue without pauses." - ); + info!("Paused at {description}. Press [Enter] to step to to the next, or [c + Enter] to continue without pauses."); tokio::task::spawn_blocking(|| { use std::io::{self, BufRead}; let stdin = io::stdin(); diff --git a/modules/mithril_snapshot_fetcher/src/pause.rs b/modules/mithril_snapshot_fetcher/src/pause.rs index e1abb649..ac8f0dd7 100644 --- a/modules/mithril_snapshot_fetcher/src/pause.rs +++ b/modules/mithril_snapshot_fetcher/src/pause.rs @@ -28,7 +28,10 @@ impl PauseType { let parts: Vec<&str> = pause_str.split(':').collect(); if parts.len() != 2 { - error!("Invalid pause format: {}. Expected format: 'type:value' (e.g., 'epoch:214', 'block:1200')", pause_str); + error!( + "Invalid pause format: {}. Expected format: 'type:value' (e.g., 'epoch:214', 'block:1200')", + pause_str + ); return None; } diff --git a/modules/rest_blockfrost/src/handlers/epochs.rs b/modules/rest_blockfrost/src/handlers/epochs.rs index 5cb9e37e..a16723a9 100644 --- a/modules/rest_blockfrost/src/handlers/epochs.rs +++ b/modules/rest_blockfrost/src/handlers/epochs.rs @@ -117,9 +117,9 @@ pub async fn handle_epoch_info_blockfrost( Message::StateQueryResponse(StateQueryResponse::Accounts( AccountsStateQueryResponse::ActiveStakes(total_active_stake), )) => Ok(Some(total_active_stake)), - Message::StateQueryResponse(StateQueryResponse::Accounts( - AccountsStateQueryResponse::Error(_), - )) => Ok(None), + Message::StateQueryResponse(StateQueryResponse::Accounts(AccountsStateQueryResponse::Error(_))) => { + Ok(None) + } _ => Err(QueryError::internal_error( "Unexpected message type while retrieving the latest total active stakes", )), @@ -129,9 +129,7 @@ pub async fn handle_epoch_info_blockfrost( } else { // Historical epoch: use SPDD if available let total_active_stakes_msg = Arc::new(Message::StateQuery(StateQuery::SPDD( - SPDDStateQuery::GetEpochTotalActiveStakes { - epoch: epoch_number, - }, + SPDDStateQuery::GetEpochTotalActiveStakes { epoch: epoch_number }, ))); query_state( &context, @@ -139,18 +137,17 @@ pub async fn handle_epoch_info_blockfrost( total_active_stakes_msg, |message| match message { Message::StateQueryResponse(StateQueryResponse::SPDD( - SPDDStateQueryResponse::EpochTotalActiveStakes(total_active_stakes), - )) => Ok(Some(total_active_stakes)), - Message::StateQueryResponse(StateQueryResponse::SPDD( - SPDDStateQueryResponse::Error(_), - )) => Ok(None), - _ => Err(QueryError::internal_error( - format!("Unexpected message type while retrieving total active stakes for epoch: {epoch_number}"), - )), + SPDDStateQueryResponse::EpochTotalActiveStakes(total_active_stakes), + )) => Ok(Some(total_active_stakes)), + Message::StateQueryResponse(StateQueryResponse::SPDD(SPDDStateQueryResponse::Error(_))) => Ok(None), + _ => Err(QueryError::internal_error(format!( + "Unexpected message type while retrieving total active stakes for epoch: {epoch_number}" + ))), }, ) - .await? - }.unwrap_or(0); + .await? + } + .unwrap_or(0); if total_active_stakes == 0 { response.active_stake = None; diff --git a/modules/stake_delta_filter/src/utils.rs b/modules/stake_delta_filter/src/utils.rs index c839a8b7..9bebc7c6 100644 --- a/modules/stake_delta_filter/src/utils.rs +++ b/modules/stake_delta_filter/src/utils.rs @@ -354,12 +354,12 @@ pub fn process_message( let stake_address = match &shelley.delegation { // Base addresses (stake delegated to itself) ShelleyAddressDelegationPart::StakeKeyHash(keyhash) => StakeAddress { - network: shelley.network.clone(), + network: shelley.network, credential: StakeCredential::AddrKeyHash(*keyhash), }, ShelleyAddressDelegationPart::ScriptHash(scripthash) => StakeAddress { - network: shelley.network.clone(), + network: shelley.network, credential: StakeCredential::ScriptHash(*scripthash), }, diff --git a/modules/tx_unpacker/Cargo.toml b/modules/tx_unpacker/Cargo.toml index 620d3641..ce3bbc0e 100644 --- a/modules/tx_unpacker/Cargo.toml +++ b/modules/tx_unpacker/Cargo.toml @@ -13,13 +13,16 @@ acropolis_common = { path = "../../common" } acropolis_codec = { path = "../../codec" } caryatid_sdk = { workspace = true } - anyhow = { workspace = true } config = { workspace = true } futures = "0.3.31" hex = { workspace = true } +tokio = { workspace = true } pallas = { workspace = true } tracing = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +test-case = "3.3.1" [lib] path = "src/tx_unpacker.rs" diff --git a/modules/tx_unpacker/VALIDATIONS.md b/modules/tx_unpacker/VALIDATIONS.md new file mode 100644 index 00000000..e206e62f --- /dev/null +++ b/modules/tx_unpacker/VALIDATIONS.md @@ -0,0 +1,173 @@ +Validate transactions phase 1 +============================= + +Haskell sources. Shelley epoch UTxO rule +---------------------------------------- + +1. Transaction validation takes place in ledger, in file +`shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs` + +Validation is performed in rule "UTXO" ("PPUP"), in function +`utxoInductive` + +This is the context of validation rules: +``` + TRC (UtxoEnv slot pp certState, utxos, tx) <- judgmentContext + let utxo = utxos ^. utxoL + UTxOState _ _ _ ppup _ _ = utxos + txBody = tx ^. bodyTxL + outputs = txBody ^. outputsTxBodyL + genDelegs = dsGenDelegs (certState ^. certDStateL) + netId <- liftSTS $ asks networkId + -- process Protocol Parameter Update Proposals + ppup' <- + trans @(EraRule "PPUP" era) $ TRC (PPUPEnv slot pp genDelegs, ppup, txBody ^. updateTxBodyL) +``` + +* Values `utxo`, `ppup`, `genDelegs` require knowledge of `judgementContext`, +that is previous state of Ledger. +* `pp` is parameters state, already sent. + +The following sub-functions are called there: +``` + {- txttl txb ≥ slot -} + runTest $ validateTimeToLive txBody slot + + {- txins txb ≠ ∅ -} + runTest $ validateInputSetEmptyUTxO txBody + + {- minfee pp tx ≤ txfee txb -} + runTest $ validateFeeTooSmallUTxO pp tx utxo + + {- txins txb ⊆ dom utxo -} + runTest $ validateBadInputsUTxO utxo $ txBody ^. inputsTxBodyL + + netId <- liftSTS $ asks networkId + + {- ∀(_ → (a, _)) ∈ txouts txb, netId a = NetworkId -} + runTest $ validateWrongNetwork netId outputs + + {- ∀(a → ) ∈ txwdrls txb, netId a = NetworkId -} + runTest $ validateWrongNetworkWithdrawal netId txBody + + {- consumed pp utxo txb = produced pp poolParams txb -} + runTest $ validateValueNotConservedUTxO pp utxo certState txBody + + -- process Protocol Parameter Update Proposals + ppup' <- + trans @(EraRule "PPUP" era) $ TRC (PPUPEnv slot pp genDelegs, ppup, txBody ^. updateTxBodyL) + + {- ∀(_ → (_, c)) ∈ txouts txb, c ≥ (minUTxOValue pp) -} + runTest $ validateOutputTooSmallUTxO pp outputs + + {- ∀ ( _ ↦ (a,_)) ∈ txoutstxb, a ∈ Addrbootstrap → bootstrapAttrsSize a ≤ 64 -} + runTest $ validateOutputBootAddrAttrsTooBig outputs + + {- txsize tx ≤ maxTxSize pp -} + runTest $ validateMaxTxSizeUTxO pp tx +``` + +2. The following checks require knowledge of ledger: + + +``` + {- txins txb ⊆ dom utxo -} + runTest $ validateBadInputsUTxO utxo $ txBody ^. inputsTxBodyL +``` + +where in `cardano-ledger-core/src/Cardano/Ledger/TxIn.hs` and +`cardano-ledger-core/src/Cardano/Ledger/State/UTxO.hs`: + +``` +-- | A unique ID of a transaction, which is computable from the transaction. +newtype TxId = TxId {unTxId :: SafeHash EraIndependentTxBody} + deriving (Show, Eq, Ord, Generic) + deriving newtype (NoThunks, ToJSON, FromJSON, HeapWords, EncCBOR, DecCBOR, NFData, MemPack) + +-- | The input of a UTxO. +data TxIn = TxIn !TxId {-# UNPACK #-} !TxIx + deriving (Generic, Eq, Ord, Show) + +-- | The unspent transaction outputs. +newtype UTxO era = UTxO {unUTxO :: Map.Map TxIn (TxOut era)} + deriving (Default, Generic, Semigroup) +``` + +Shelley checks for UTXOW, rule "UTXOW" +-------------------------------------- + +``` + -- * Individual validation steps + validateFailedNativeScripts, + validateMissingScripts, + validateVerifiedWits, + validateMetadata, + validateMIRInsufficientGenesisSigs, + validateNeededWitnesses, +``` + +``` +transitionRulesUTXOW = do + (TRC (utxoEnv@(UtxoEnv _ pp certState), u, tx)) <- judgmentContext + + {- (utxo,_,_,_ ) := utxoSt -} + {- witsKeyHashes := { hashKey vk | vk ∈ dom(txwitsVKey txw) } -} + let utxo = utxosUtxo u + witsKeyHashes = witsFromTxWitnesses tx + scriptsProvided = getScriptsProvided utxo tx + + -- check scripts + {- ∀ s ∈ range(txscripts txw) ∩ Scriptnative), runNativeScript s tx -} + + runTestOnSignal $ validateFailedNativeScripts scriptsProvided tx + + {- { s | (_,s) ∈ scriptsNeeded utxo tx} = dom(txscripts txw) -} + let scriptsNeeded = getScriptsNeeded utxo (tx ^. bodyTxL) + runTest $ validateMissingScripts scriptsNeeded scriptsProvided + + -- check VKey witnesses + {- ∀ (vk ↦ σ) ∈ (txwitsVKey txw), V_vk⟦ txbodyHash ⟧_σ -} + runTestOnSignal $ validateVerifiedWits tx + + {- witsVKeyNeeded utxo tx genDelegs ⊆ witsKeyHashes -} + runTest $ validateNeededWitnesses witsKeyHashes certState utxo (tx ^. bodyTxL) + + -- check metadata hash + {- ((adh = â—‡) ∧ (ad= â—‡)) ∨ (adh = hashAD ad) -} + runTestOnSignal $ validateMetadata pp tx + + -- check genesis keys signatures for instantaneous rewards certificates + {- genSig := { hashKey gkey | gkey ∈ dom(genDelegs)} ∩ witsKeyHashes -} + {- { c ∈ txcerts txb ∩ TxCert_mir} ≠ ∅ ⇒ (|genSig| ≥ Quorum) ∧ (d pp > 0) -} + let genDelegs = dsGenDelegs (certState ^. certDStateL) + coreNodeQuorum <- liftSTS $ asks quorum + runTest $ + validateMIRInsufficientGenesisSigs genDelegs coreNodeQuorum witsKeyHashes tx + + trans @(EraRule "UTXO" era) $ TRC (utxoEnv, u, tx) +``` + + +Some notes from consensus meeting +--------------------------------- + +applyTx -- 1st phase --- applyShelleyTx + dig into applyShelleyBaseTx + applyAlonzoBasedTx + -- 2nd phase + + Core.Tx ==> has 'isValid' code for transaction + defaultApplyShelleyBasedTx (basic function from Shelley?) + +reapplyShelleyTx -- skips some checks (e.g. signatures, +if user keys didn't change; does not scripts again); we see +validity interval + * Applied same transactions to different Ledger state + * If my selection changes, I need to revalidate all + transactions. + +everything applies to Shelley. +Either Byron, or Shelley. + +Block diagram of a full block (Ouroboros consensus) +... intersect-mbo.org ... diff --git a/modules/tx_unpacker/src/state.rs b/modules/tx_unpacker/src/state.rs new file mode 100644 index 00000000..4d1966e0 --- /dev/null +++ b/modules/tx_unpacker/src/state.rs @@ -0,0 +1,47 @@ +use crate::{utxo_registry::UTxORegistry, validations}; +use acropolis_common::{ + messages::ProtocolParamsMessage, protocol_params::ProtocolParams, + validation::TransactionValidationError, BlockInfo, Era, +}; +use anyhow::Result; + +#[derive(Default, Clone)] +pub struct State { + pub protocol_params: ProtocolParams, +} + +impl State { + pub fn new() -> Self { + Self { + protocol_params: ProtocolParams::default(), + } + } + + pub fn handle_protocol_params(&mut self, msg: &ProtocolParamsMessage) { + self.protocol_params = msg.params.clone(); + } + + pub fn validate_transaction( + &self, + block_info: &BlockInfo, + raw_tx: &[u8], + utxo_registry: &UTxORegistry, + ) -> Result<(), TransactionValidationError> { + match block_info.era { + Era::Shelley => { + let Some(shelley_params) = self.protocol_params.shelley.as_ref() else { + return Err(TransactionValidationError::Other( + "Shelley params are not set".to_string(), + )); + }; + validations::validate_shelley_tx( + raw_tx, + shelley_params, + block_info.slot, + |tx_ref| utxo_registry.lookup_by_hash(tx_ref), + ) + } + _ => Ok(()), + } + } +} diff --git a/modules/tx_unpacker/src/test_utils.rs b/modules/tx_unpacker/src/test_utils.rs new file mode 100644 index 00000000..109773bf --- /dev/null +++ b/modules/tx_unpacker/src/test_utils.rs @@ -0,0 +1,76 @@ +use acropolis_common::{protocol_params::ShelleyParams, Slot, TxHash, TxIdentifier, TxOutRef}; +use std::{collections::HashMap, str::FromStr}; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TestContextJson { + pub shelley_params: ShelleyParams, + pub current_slot: Slot, + // Vec<((TxHash, TxIndex), (BlockNumber, TxIndex))> + pub utxos: Vec<((String, u16), (u32, u16))>, +} + +#[derive(Debug)] +pub struct TestContext { + pub shelley_params: ShelleyParams, + pub current_slot: Slot, + pub utxos: HashMap, +} + +impl From for TestContext { + fn from(json: TestContextJson) -> Self { + Self { + shelley_params: json.shelley_params, + current_slot: json.current_slot, + utxos: json + .utxos + .into_iter() + .map(|((tx_hash, output_index), (block_number, tx_index))| { + ( + TxOutRef::new(TxHash::from_str(&tx_hash).unwrap(), output_index), + TxIdentifier::new(block_number, tx_index), + ) + }) + .collect(), + } + } +} +#[macro_export] +macro_rules! include_cbor { + ($filepath:expr) => { + hex::decode(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/data/", + $filepath, + ))) + .expect(concat!("invalid cbor file: ", $filepath)) + }; +} + +#[macro_export] +macro_rules! include_context { + ($filepath:expr) => { + serde_json::from_str::<$crate::test_utils::TestContextJson>(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/data/", + $filepath, + ))) + .expect(concat!("invalid context file: ", $filepath)) + .into() + }; +} + +#[macro_export] +macro_rules! validation_fixture { + ($hash:literal) => { + ( + $crate::include_context!(concat!($hash, "/context.json")), + $crate::include_cbor!(concat!($hash, "/tx.cbor")), + ) + }; + ($hash:literal, $variant:literal) => { + ( + $crate::include_context!(concat!($hash, "/", "/context.json")), + $crate::include_cbor!(concat!($hash, "/", $variant, ".cbor")), + ) + }; +} diff --git a/modules/tx_unpacker/src/tx_unpacker.rs b/modules/tx_unpacker/src/tx_unpacker.rs index 296be7e3..0ce794a2 100644 --- a/modules/tx_unpacker/src/tx_unpacker.rs +++ b/modules/tx_unpacker/src/tx_unpacker.rs @@ -7,24 +7,30 @@ use acropolis_common::{ AssetDeltasMessage, BlockTxsMessage, CardanoMessage, GovernanceProceduresMessage, Message, TxCertificatesMessage, UTXODeltasMessage, WithdrawalsMessage, }, + state_history::{StateHistory, StateHistoryStore}, *, }; -use caryatid_sdk::{module, Context}; -use std::{clone::Clone, fmt::Debug, sync::Arc}; - use anyhow::Result; +use caryatid_sdk::{module, Context, Subscription}; use config::Config; use futures::future::join_all; use pallas::codec::minicbor::encode; use pallas::ledger::primitives::KeyValuePairs; use pallas::ledger::{primitives, traverse, traverse::MultiEraTx}; -use tracing::{debug, error, info, info_span, Instrument}; - +use std::{clone::Clone, fmt::Debug, sync::Arc}; +use tokio::sync::Mutex; +use tracing::{debug, error, info, info_span}; +mod state; mod utxo_registry; -use crate::utxo_registry::UTxORegistry; +mod validations; +use crate::{state::State, utxo_registry::UTxORegistry}; + +#[cfg(test)] +mod test_utils; const DEFAULT_TRANSACTIONS_SUBSCRIBE_TOPIC: &str = "cardano.txs"; const DEFAULT_GENESIS_SUBSCRIBE_TOPIC: &str = "cardano.genesis.utxos"; +const DEFAULT_PROTOCOL_PARAMS_SUBSCRIBE_TOPIC: &str = "cardano.protocol.parameters"; const CIP25_METADATA_LABEL: u64 = 721; @@ -38,439 +44,561 @@ const CIP25_METADATA_LABEL: u64 = 721; pub struct TxUnpacker; impl TxUnpacker { - fn decode_updates( - dest: &mut Vec, - proposals: &KeyValuePairs, - epoch: u64, - map: impl Fn(&EraSpecificUpdateProposals) -> Result>, - ) { - let mut update = AlonzoBabbageUpdateProposal { - proposals: Vec::new(), - enactment_epoch: epoch, - }; - - for (hash_bytes, vote) in proposals.iter() { - let hash = match GenesisKeyhash::try_from(hash_bytes.as_ref()) { - Ok(h) => h, - Err(e) => { - error!("Invalid genesis keyhash in protocol parameter update: {e}"); - continue; - } - }; - - match map(vote) { - Ok(upd) => update.proposals.push((hash, upd)), - Err(e) => error!("Cannot convert protocol param update {vote:?}: {e}"), + #[allow(clippy::too_many_arguments)] + async fn run( + context: Arc>, + network_id: NetworkId, + history: Arc>>, + mut utxo_registry: UTxORegistry, + // publishers + publish_utxo_deltas_topic: Option, + publish_asset_deltas_topic: Option, + publish_withdrawals_topic: Option, + publish_certificates_topic: Option, + publish_governance_procedures_topic: Option, + publish_block_txs_topic: Option, + // subscribers + mut genesis_sub: Box>, + mut txs_sub: Box>, + mut protocol_params_sub: Box>, + ) -> Result<()> { + // Initialize TxRegistry with genesis utxos + let (_, message) = genesis_sub.read().await.expect("failed to read genesis utxos"); + match message.as_ref() { + Message::Cardano((_block, CardanoMessage::GenesisUTxOs(genesis_msg))) => { + utxo_registry.bootstrap_from_genesis_utxos(&genesis_msg.utxos); + info!( + "Seeded registry with {} genesis utxos", + genesis_msg.utxos.len() + ); } + other => panic!("expected GenesisUTxOs, got {:?}", other), } - dest.push(update); - } - /// Main init function - pub async fn init(&self, context: Arc>, config: Arc) -> Result<()> { - // Get configuration - let transactions_subscribe_topic = config - .get_string("subscribe-topic") - .unwrap_or(DEFAULT_TRANSACTIONS_SUBSCRIBE_TOPIC.to_string()); - info!("Creating subscriber on '{transactions_subscribe_topic}'"); - - let genesis_utxos_subscribe_topic = config - .get_string("genesis-utxos-subscribe-topic") - .unwrap_or(DEFAULT_GENESIS_SUBSCRIBE_TOPIC.to_string()); - info!("Creating subscriber on '{genesis_utxos_subscribe_topic}'"); - - let publish_utxo_deltas_topic = config.get_string("publish-utxo-deltas-topic").ok(); - if let Some(ref topic) = publish_utxo_deltas_topic { - info!("Publishing UTXO deltas on '{topic}'"); - } - - let publish_asset_deltas_topic = config.get_string("publish-asset-deltas-topic").ok(); - if let Some(ref topic) = publish_asset_deltas_topic { - info!("Publishing native asset deltas on '{topic}'"); - } - - let publish_withdrawals_topic = config.get_string("publish-withdrawals-topic").ok(); - if let Some(ref topic) = publish_withdrawals_topic { - info!("Publishing withdrawals on '{topic}'"); - } + loop { + let mut state = history.lock().await.get_or_init_with(State::new); + let mut current_block: Option = None; - let publish_certificates_topic = config.get_string("publish-certificates-topic").ok(); - if let Some(ref topic) = publish_certificates_topic { - info!("Publishing certificates on '{topic}'"); - } - - let publish_governance_procedures_topic = - config.get_string("publish-governance-topic").ok(); - if let Some(ref topic) = publish_governance_procedures_topic { - info!("Publishing governance procedures on '{topic}'"); - } - - let publish_block_txs_topic = config.get_string("publish-block-txs-topic").ok(); - if let Some(ref topic) = publish_block_txs_topic { - info!("Publishing block txs on '{topic}'"); - } - - let network_id: NetworkId = - config.get_string("network-id").unwrap_or("mainnet".to_string()).into(); - - // Initialize UTxORegistry - let mut utxo_registry = UTxORegistry::default(); + let Ok((_, message)) = txs_sub.read().await else { + return Err(anyhow::anyhow!("failed to read txs")); + }; + let new_epoch = match message.as_ref() { + Message::Cardano((block_info, _)) => { + // Handle rollbacks on this topic only + if block_info.status == BlockStatus::RolledBack { + state = history.lock().await.get_rolled_back_state(block_info.number); + } + current_block = Some(block_info.clone()); - // Subscribe to genesis and txs topics - let mut genesis_sub = context.subscribe(&genesis_utxos_subscribe_topic).await?; - let mut txs_sub = context.subscribe(&transactions_subscribe_topic).await?; + // new_epoch? + block_info.new_epoch + } - context.clone().run(async move { - // Initialize TxRegistry with genesis utxos - let (_, message) = genesis_sub.read().await - .expect("failed to read genesis utxos"); - match message.as_ref() { - Message::Cardano((_block, CardanoMessage::GenesisUTxOs(genesis_msg))) => { - utxo_registry.bootstrap_from_genesis_utxos(&genesis_msg.utxos); - info!("Seeded registry with {} genesis utxos", genesis_msg.utxos.len()); + _ => { + error!("Unexpected message type: {message:?}"); + false } - other => panic!("expected GenesisUTxOs, got {:?}", other), - } - loop { - let Ok((_, message)) = txs_sub.read().await else { return; }; - match message.as_ref() { - Message::Cardano((block, CardanoMessage::ReceivedTxs(txs_msg))) => { - let span = info_span!("tx_unpacker.run", block = block.number); - - async { - if tracing::enabled!(tracing::Level::DEBUG) { - debug!("Received {} txs for slot {}", - txs_msg.txs.len(), block.slot); - } + }; - let mut utxo_deltas = Vec::new(); - let mut asset_deltas = Vec::new(); - let mut cip25_metadata_updates = Vec::new(); - let mut withdrawals = Vec::new(); - let mut certificates = Vec::new(); - let mut voting_procedures = Vec::new(); - let mut proposal_procedures = Vec::new(); - let mut alonzo_babbage_update_proposals = Vec::new(); - let mut total_output: u128 = 0; - let mut total_fees: u64 = 0; - let total_txs = txs_msg.txs.len() as u64; - - // handle rollback or advance registry to the next block - let block_number = block.number as u32; - if block.status == BlockStatus::RolledBack { - if let Err(e) = utxo_registry.rollback_before(block_number) { - error!("rollback_before({}) failed: {}", block_number, e); - } - utxo_registry.next_block(); - } + match message.as_ref() { + Message::Cardano((block, CardanoMessage::ReceivedTxs(txs_msg))) => { + if tracing::enabled!(tracing::Level::DEBUG) { + debug!("Received {} txs for slot {}", txs_msg.txs.len(), block.slot); + } - for (tx_index , raw_tx) in txs_msg.txs.iter().enumerate() { - let tx_index = tx_index as u16; - - // Parse the tx - match MultiEraTx::decode(raw_tx) { - Ok(tx) => { - let tx_hash: TxHash = tx.hash().to_vec().try_into().expect("invalid tx hash length"); - let tx_identifier = TxIdentifier::new(block_number, tx_index); - - let inputs = tx.consumes(); - let outputs = tx.produces(); - let certs = tx.certs(); - let tx_withdrawals = tx.withdrawals_sorted_set(); - let mut props = None; - let mut votes = None; - - if tracing::enabled!(tracing::Level::DEBUG) { - debug!("Decoded tx with {} inputs, {} outputs, {} certs", - inputs.len(), outputs.len(), certs.len()); - } + // handle rollback or advance registry to the next block + let block_number = block.number as u32; + if block.status == BlockStatus::RolledBack { + if let Err(e) = utxo_registry.rollback_before(block_number) { + error!("rollback_before({}) failed: {}", block_number, e); + } + utxo_registry.next_block(); + state = history.lock().await.get_rolled_back_state(block.number); + current_block = Some(block.clone()); + } - if publish_utxo_deltas_topic.is_some() { - // Group deltas by tx - let mut tx_utxo_deltas = TxUTxODeltas {tx_identifier, inputs: Vec::new(), outputs: Vec::new()}; - - // Remove inputs from UTxORegistry and push to UTxOIdentifiers to delta - for input in inputs { - let oref = input.output_ref(); - let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16); - - match utxo_registry.consume(&tx_ref) { - Ok(tx_identifier) => { - tx_utxo_deltas.inputs.push( - UTxOIdentifier::new( - tx_identifier.block_number(), - tx_identifier.tx_index(), - tx_ref.output_index, - ), - ); - } - Err(e) => { - error!("Failed to consume input {}: {e}", tx_ref.output_index); - } + let mut utxo_deltas = Vec::new(); + let mut asset_deltas = Vec::new(); + let mut cip25_metadata_updates = Vec::new(); + let mut withdrawals = Vec::new(); + let mut certificates = Vec::new(); + let mut voting_procedures = Vec::new(); + let mut proposal_procedures = Vec::new(); + let mut alonzo_babbage_update_proposals = Vec::new(); + let mut total_output: u128 = 0; + let mut total_fees: u64 = 0; + let total_txs = txs_msg.txs.len() as u64; + + let span = info_span!("tx_unpacker.handle_txs", block = block.number); + span.in_scope(|| { + for (tx_index, raw_tx) in txs_msg.txs.iter().enumerate() { + let tx_index = tx_index as u16; + + // Validate transaction + if let Err(e) = state.validate_transaction(block, raw_tx, &utxo_registry) { + error!("Failed to validate transaction; tx_index={}, block={}: {e}", tx_index, block.number); + }; + + // Parse the tx + match MultiEraTx::decode(raw_tx) { + Ok(tx) => { + let tx_hash: TxHash = + tx.hash().to_vec().try_into().expect("invalid tx hash length"); + let tx_identifier = TxIdentifier::new(block_number, tx_index); + + let certs = tx.certs(); + let tx_withdrawals = tx.withdrawals_sorted_set(); + let mut props = None; + let mut votes = None; + + let (tx_inputs, tx_outputs, errors) = + map_parameters::map_transaction_inputs_outputs( + block_number, + tx_index, + &tx, + ); + + if tracing::enabled!(tracing::Level::DEBUG) { + debug!( + "Decoded tx with {} inputs, {} outputs, {} certs, {} errors", + tx_inputs.len(), + tx_outputs.len(), + certs.len(), + errors.len() + ) + } + + if publish_utxo_deltas_topic.is_some() { + // Lookup and remove UTxOIdentifier from registry + // Group deltas by tx + let mut tx_utxo_deltas = TxUTxODeltas { + tx_identifier, + inputs: Vec::new(), + outputs: Vec::new(), + }; + + // Remove inputs from UTxORegistry and push to UTxOIdentifiers to delta + for tx_ref in tx_inputs { + match utxo_registry.consume(&tx_ref) { + Ok(tx_identifier) => { + // Add TxInput to utxo_deltas + tx_utxo_deltas.inputs.push(UTxOIdentifier::new( + tx_identifier.block_number(), + tx_identifier.tx_index(), + tx_ref.output_index, + )); } - } - - // Add outputs to UTxORegistry and push TxOutputs to delta - for (index, output) in outputs { - match utxo_registry.add( - block_number, - tx_index, - TxOutRef { - tx_hash, - output_index: index as u16, - }, - ) { - Ok(utxo_id) => { - match output.address() { - Ok(pallas_address) => match map_parameters::map_address(&pallas_address) { - Ok(address) => { - tx_utxo_deltas.outputs.push(TxOutput { - utxo_identifier: utxo_id, - address, - value: map_parameters::map_value(&output.value()), - datum: map_parameters::map_datum(&output.datum()), - reference_script: map_parameters::map_reference_script(&output.script_ref()) - }); - - // catch all output lovelaces - total_output += output.value().coin() as u128; - } - Err(e) => error!("Output {index} in tx ignored: {e}"), - }, - Err(e) => error!("Can't parse output {index} in tx: {e}"), - } - } - Err(e) => { - error!("Failed to insert output into registry: {e}"); - } + Err(e) => { + error!( + "Failed to consume input {}: {e}", + tx_ref.output_index + ); } } - utxo_deltas.push(tx_utxo_deltas); } - if publish_asset_deltas_topic.is_some() { - let mut tx_deltas: Vec<(PolicyId, Vec)> = Vec::new(); + for (tx_ref, output) in tx_outputs.into_iter() { + total_output += output.value.coin() as u128; - // Mint deltas - for policy_group in tx.mints().iter() { - if let Some((policy_id, deltas)) = map_parameters::map_mint_burn(policy_group) { - tx_deltas.push((policy_id, deltas)); - } + if let Err(e) = + utxo_registry.add(block_number, tx_index, tx_ref) + { + error!("Failed to insert output into registry: {e}"); + } else { + tx_utxo_deltas.outputs.push(output); } + } - if let Some(metadata) = tx.metadata().find(CIP25_METADATA_LABEL) { - let mut metadata_raw = Vec::new(); - match encode(metadata, &mut metadata_raw) { - Ok(()) => { - cip25_metadata_updates.push(metadata_raw); - } - Err(e) => { - error!("failed to encode CIP-25 metadatum: {e:#}"); - } - } - } + utxo_deltas.push(tx_utxo_deltas); + } + + if publish_asset_deltas_topic.is_some() { + let mut tx_deltas: Vec<(PolicyId, Vec)> = + Vec::new(); - if !tx_deltas.is_empty() { - asset_deltas.push((tx_identifier, tx_deltas)); + // Mint deltas + for policy_group in tx.mints().iter() { + if let Some((policy_id, deltas)) = + map_parameters::map_mint_burn(policy_group) + { + tx_deltas.push((policy_id, deltas)); } } - if publish_certificates_topic.is_some() { - for ( cert_index, cert) in certs.iter().enumerate() { - match map_parameters::map_certificate(cert, tx_identifier, cert_index, network_id.clone()) { - Ok(tx_cert) => { - certificates.push(tx_cert); - }, - Err(_e) => { - // TODO error unexpected - //error!("{e}"); - } + if let Some(metadata) = tx.metadata().find(CIP25_METADATA_LABEL) + { + let mut metadata_raw = Vec::new(); + match encode(metadata, &mut metadata_raw) { + Ok(()) => { + cip25_metadata_updates.push(metadata_raw); + } + Err(e) => { + error!("failed to encode CIP-25 metadatum: {e:#}"); } } } - if publish_withdrawals_topic.is_some() { - for (key, value) in tx_withdrawals { - match StakeAddress::from_binary(key) { - Ok(stake_address) => { - withdrawals.push(Withdrawal { - address: stake_address, - value, - tx_identifier - }); - } - Err(e) => error!("Bad stake address: {e:#}"), + if !tx_deltas.is_empty() { + asset_deltas.push((tx_identifier, tx_deltas)); + } + } + + if publish_certificates_topic.is_some() { + for (cert_index, cert) in certs.iter().enumerate() { + match map_parameters::map_certificate( + cert, + tx_identifier, + cert_index, + network_id, + ) { + Ok(tx_cert) => { + certificates.push(tx_cert); + } + Err(_e) => { + // TODO error unexpected + //error!("{e}"); } } } - - if publish_governance_procedures_topic.is_some() { - //Self::decode_legacy_updates(&mut legacy_update_proposals, &block, &raw_tx); - if block.era >= Era::Shelley && block.era < Era::Babbage { - if let Ok(alonzo) = MultiEraTx::decode_for_era(traverse::Era::Alonzo, raw_tx) { - if let Some(update) = alonzo.update() { - if let Some(alonzo_update) = update.as_alonzo() { - Self::decode_updates( + } + + if publish_withdrawals_topic.is_some() { + for (key, value) in tx_withdrawals { + match StakeAddress::from_binary(key) { + Ok(stake_address) => { + withdrawals.push(Withdrawal { + address: stake_address, + value, + tx_identifier, + }); + } + Err(e) => error!("Bad stake address: {e:#}"), + } + } + } + + if publish_governance_procedures_topic.is_some() { + //Self::decode_legacy_updates(&mut legacy_update_proposals, &block, &raw_tx); + if block.era >= Era::Shelley && block.era < Era::Babbage { + if let Ok(alonzo) = MultiEraTx::decode_for_era( + traverse::Era::Alonzo, + raw_tx, + ) { + if let Some(update) = alonzo.update() { + if let Some(alonzo_update) = update.as_alonzo() { + Self::decode_updates( &mut alonzo_babbage_update_proposals, &alonzo_update.proposed_protocol_parameter_updates, alonzo_update.epoch, map_parameters::map_alonzo_protocol_param_update ); - } } } } - else if block.era >= Era::Babbage && block.era < Era::Conway{ - if let Ok(babbage) = MultiEraTx::decode_for_era(traverse::Era::Babbage, raw_tx) { - if let Some(update) = babbage.update() { - if let Some(babbage_update) = update.as_babbage() { - Self::decode_updates( + } else if block.era >= Era::Babbage && block.era < Era::Conway { + if let Ok(babbage) = MultiEraTx::decode_for_era( + traverse::Era::Babbage, + raw_tx, + ) { + if let Some(update) = babbage.update() { + if let Some(babbage_update) = update.as_babbage() { + Self::decode_updates( &mut alonzo_babbage_update_proposals, &babbage_update.proposed_protocol_parameter_updates, babbage_update.epoch, map_parameters::map_babbage_protocol_param_update ); - } } } } } + } - if let Some(conway) = tx.as_conway() { - if let Some(ref v) = conway.transaction_body.voting_procedures { - votes = Some(v); - } - - if let Some(ref p) = conway.transaction_body.proposal_procedures { - props = Some(p); - } + if let Some(conway) = tx.as_conway() { + if let Some(ref v) = conway.transaction_body.voting_procedures { + votes = Some(v); } - - if publish_governance_procedures_topic.is_some() { - if let Some(pp) = props { - // Nonempty set -- governance_message.proposal_procedures will not be empty - let mut proc_id = GovActionId { transaction_id: tx_hash, action_index: 0 }; - for (action_index, pallas_governance_proposals) in pp.iter().enumerate() { - match proc_id.set_action_index(action_index) + if let Some(ref p) = conway.transaction_body.proposal_procedures + { + props = Some(p); + } + } + + if publish_governance_procedures_topic.is_some() { + if let Some(pp) = props { + // Nonempty set -- governance_message.proposal_procedures will not be empty + let mut proc_id = GovActionId { + transaction_id: tx_hash, + action_index: 0, + }; + for (action_index, pallas_governance_proposals) in + pp.iter().enumerate() + { + match proc_id.set_action_index(action_index) .and_then (|proc_id| map_parameters::map_governance_proposals_procedures(proc_id, pallas_governance_proposals)) { Ok(g) => proposal_procedures.push(g), Err(e) => error!("Cannot decode governance proposal procedure {} idx {} in slot {}: {e}", proc_id, action_index, block.slot) } - } } + } - if let Some(pallas_vp) = votes { - // Nonempty set -- governance_message.voting_procedures will not be empty - match map_parameters::map_all_governance_voting_procedures(pallas_vp) { + if let Some(pallas_vp) = votes { + // Nonempty set -- governance_message.voting_procedures will not be empty + match map_parameters::map_all_governance_voting_procedures(pallas_vp) { Ok(vp) => voting_procedures.push((tx_hash, vp)), Err(e) => error!("Cannot decode governance voting procedures in slot {}: {e}", block.slot) } - } } + } - // Capture the fees - if let Some(fee) = tx.fee() { - total_fees += fee; - } - }, + // Capture the fees + if let Some(fee) = tx.fee() { + total_fees += fee; + } + } - Err(e) => error!("Can't decode transaction in slot {}: {e}", - block.slot) + Err(e) => { + error!("Can't decode transaction in slot {}: {e}", block.slot) } } + } + }); + + utxo_registry.next_block(); + + // Publish messages in parallel + let mut futures = Vec::new(); + if let Some(ref topic) = publish_utxo_deltas_topic { + let msg = Message::Cardano(( + block.clone(), + CardanoMessage::UTXODeltas(UTXODeltasMessage { + deltas: utxo_deltas, + }), + )); + + futures.push(context.message_bus.publish(topic, Arc::new(msg))); + } - utxo_registry.next_block(); + if let Some(ref topic) = publish_asset_deltas_topic { + let msg = Message::Cardano(( + block.clone(), + CardanoMessage::AssetDeltas(AssetDeltasMessage { + deltas: asset_deltas, + cip25_metadata_updates, + }), + )); - // Publish messages in parallel - let mut futures = Vec::new(); - if let Some(ref topic) = publish_utxo_deltas_topic { - let msg = Message::Cardano(( - block.clone(), - CardanoMessage::UTXODeltas(UTXODeltasMessage { - deltas: utxo_deltas, - }) - )); + futures.push(context.message_bus.publish(topic, Arc::new(msg))); + } - futures.push(context.message_bus.publish(topic, Arc::new(msg))); - } + if let Some(ref topic) = publish_withdrawals_topic { + let msg = Message::Cardano(( + block.clone(), + CardanoMessage::Withdrawals(WithdrawalsMessage { withdrawals }), + )); - if let Some(ref topic) = publish_asset_deltas_topic { - let msg = Message::Cardano(( - block.clone(), - CardanoMessage::AssetDeltas(AssetDeltasMessage { - deltas: asset_deltas, - cip25_metadata_updates - }) - )); + futures.push(context.message_bus.publish(topic, Arc::new(msg))); + } - futures.push(context.message_bus.publish(topic, Arc::new(msg))); - } + if let Some(ref topic) = publish_certificates_topic { + let msg = Message::Cardano(( + block.clone(), + CardanoMessage::TxCertificates(TxCertificatesMessage { certificates }), + )); - if let Some(ref topic) = publish_withdrawals_topic { - let msg = Message::Cardano(( - block.clone(), - CardanoMessage::Withdrawals(WithdrawalsMessage { - withdrawals, - }) - )); + futures.push(context.message_bus.publish(topic, Arc::new(msg))); + } - futures.push(context.message_bus.publish(topic, Arc::new(msg))); - } + if let Some(ref topic) = publish_governance_procedures_topic { + let governance_msg = Arc::new(Message::Cardano(( + block.clone(), + CardanoMessage::GovernanceProcedures(GovernanceProceduresMessage { + voting_procedures, + proposal_procedures, + alonzo_babbage_updates: alonzo_babbage_update_proposals, + }), + ))); + + futures.push(context.message_bus.publish(topic, governance_msg.clone())); + } - if let Some(ref topic) = publish_certificates_topic { - let msg = Message::Cardano(( - block.clone(), - CardanoMessage::TxCertificates(TxCertificatesMessage { - certificates, - }) - )); + if let Some(ref topic) = publish_block_txs_topic { + let msg = Message::Cardano(( + block.clone(), + CardanoMessage::BlockInfoMessage(BlockTxsMessage { + total_txs, + total_output, + total_fees, + }), + )); + + futures.push(context.message_bus.publish(topic, Arc::new(msg))); + } - futures.push(context.message_bus.publish(topic, Arc::new(msg))); - } + join_all(futures) + .await + .into_iter() + .filter_map(Result::err) + .for_each(|e| error!("Failed to publish: {e}")); + } - if let Some(ref topic) = publish_governance_procedures_topic { - let governance_msg = Arc::new(Message::Cardano(( - block.clone(), - CardanoMessage::GovernanceProcedures( - GovernanceProceduresMessage { - voting_procedures, - proposal_procedures, - alonzo_babbage_updates: alonzo_babbage_update_proposals - }) - ))); - - futures.push(context.message_bus.publish(topic, - governance_msg.clone())); - } + _ => error!("Unexpected message type: {message:?}"), + } - if let Some(ref topic) = publish_block_txs_topic { - let msg = Message::Cardano(( - block.clone(), - CardanoMessage::BlockInfoMessage(BlockTxsMessage { - total_txs, - total_output, - total_fees - }) - )); - - futures.push(context.message_bus.publish(topic, Arc::new(msg))); - } + if new_epoch { + let (_, protocol_parameters_msg) = protocol_params_sub.read().await?; + if let Message::Cardano((block_info, CardanoMessage::ProtocolParams(params))) = + protocol_parameters_msg.as_ref() + { + Self::check_sync(¤t_block, block_info); + let span = info_span!( + "tx_unpacker.handle_protocol_params", + block = block_info.number + ); + span.in_scope(|| { + state.handle_protocol_params(params); + }); + } + } - join_all(futures) - .await - .into_iter() - .filter_map(Result::err) - .for_each(|e| error!("Failed to publish: {e}")); - }.instrument(span).await; - } + // Commit the new state + if let Some(block_info) = current_block { + history.lock().await.commit(block_info.number, state); + } + } + } + + fn decode_updates( + dest: &mut Vec, + proposals: &KeyValuePairs, + epoch: u64, + map: impl Fn(&EraSpecificUpdateProposals) -> Result>, + ) { + let mut update = AlonzoBabbageUpdateProposal { + proposals: Vec::new(), + enactment_epoch: epoch, + }; - _ => error!("Unexpected message type: {message:?}") + for (hash_bytes, vote) in proposals.iter() { + let hash = match GenesisKeyhash::try_from(hash_bytes.as_ref()) { + Ok(h) => h, + Err(e) => { + error!("Invalid genesis keyhash in protocol parameter update: {e}"); + continue; } + }; + + match map(vote) { + Ok(upd) => update.proposals.push((hash, upd)), + Err(e) => error!("Cannot convert protocol param update {vote:?}: {e}"), } + } + + dest.push(update); + } + + /// Main init function + pub async fn init(&self, context: Arc>, config: Arc) -> Result<()> { + // Publishers + let publish_utxo_deltas_topic = config.get_string("publish-utxo-deltas-topic").ok(); + if let Some(ref topic) = publish_utxo_deltas_topic { + info!("Publishing UTXO deltas on '{topic}'"); + } + + let publish_asset_deltas_topic = config.get_string("publish-asset-deltas-topic").ok(); + if let Some(ref topic) = publish_asset_deltas_topic { + info!("Publishing native asset deltas on '{topic}'"); + } + + let publish_withdrawals_topic = config.get_string("publish-withdrawals-topic").ok(); + if let Some(ref topic) = publish_withdrawals_topic { + info!("Publishing withdrawals on '{topic}'"); + } + + let publish_certificates_topic = config.get_string("publish-certificates-topic").ok(); + if let Some(ref topic) = publish_certificates_topic { + info!("Publishing certificates on '{topic}'"); + } + + let publish_governance_procedures_topic = + config.get_string("publish-governance-topic").ok(); + if let Some(ref topic) = publish_governance_procedures_topic { + info!("Publishing governance procedures on '{topic}'"); + } + + let publish_block_txs_topic = config.get_string("publish-block-txs-topic").ok(); + if let Some(ref topic) = publish_block_txs_topic { + info!("Publishing block txs on '{topic}'"); + } + + // Subscribers + let genesis_utxos_subscribe_topic = config + .get_string("genesis-utxos-subscribe-topic") + .unwrap_or(DEFAULT_GENESIS_SUBSCRIBE_TOPIC.to_string()); + info!("Creating subscriber on '{genesis_utxos_subscribe_topic}'"); + + let transactions_subscribe_topic = config + .get_string("subscribe-topic") + .unwrap_or(DEFAULT_TRANSACTIONS_SUBSCRIBE_TOPIC.to_string()); + info!("Creating subscriber on '{transactions_subscribe_topic}'"); + + let protocol_params_subscribe_topic = config + .get_string("protocol-params-subscribe-topic") + .unwrap_or(DEFAULT_PROTOCOL_PARAMS_SUBSCRIBE_TOPIC.to_string()); + info!("Creating subscriber on '{protocol_params_subscribe_topic}'"); + + let genesis_sub = context.subscribe(&genesis_utxos_subscribe_topic).await?; + let txs_sub = context.subscribe(&transactions_subscribe_topic).await?; + let protocol_params_sub = context.subscribe(&protocol_params_subscribe_topic).await?; + + let network_id: NetworkId = + config.get_string("network-id").unwrap_or("mainnet".to_string()).into(); + + // Initialize State + let history = Arc::new(Mutex::new(StateHistory::::new( + "tx_unpacker", + StateHistoryStore::default_block_store(), + ))); + + // Initialize UTxORegistry + let utxo_registry = UTxORegistry::default(); + + let context_run = context.clone(); + context.run(async move { + Self::run( + context_run, + network_id, + history, + utxo_registry, + publish_utxo_deltas_topic, + publish_asset_deltas_topic, + publish_withdrawals_topic, + publish_certificates_topic, + publish_governance_procedures_topic, + publish_block_txs_topic, + genesis_sub, + txs_sub, + protocol_params_sub, + ) + .await + .unwrap_or_else(|e| error!("Failed to run Tx Unpacker: {e}")); }); Ok(()) } + + /// Check for synchronisation + fn check_sync(expected: &Option, actual: &BlockInfo) { + if let Some(ref block) = expected { + if block.number != actual.number { + error!( + expected = block.number, + actual = actual.number, + "Messages out of sync" + ); + } + } + } } diff --git a/modules/tx_unpacker/src/utxo_registry.rs b/modules/tx_unpacker/src/utxo_registry.rs index bdd488d0..c4f8d785 100644 --- a/modules/tx_unpacker/src/utxo_registry.rs +++ b/modules/tx_unpacker/src/utxo_registry.rs @@ -150,6 +150,16 @@ impl UTxORegistry { } } + /// Lookup a TxOutRef and return its identifier + pub fn lookup_by_hash(&self, tx_ref: TxOutRef) -> Result { + self.live_map.get(&tx_ref).copied().ok_or_else(|| { + anyhow::anyhow!( + "TxHash not found or already spent: {:?}", + hex::encode(tx_ref.tx_hash) + ) + }) + } + /// Rollback to block N-1 pub fn rollback_before(&mut self, block_number: u32) -> Result<(), String> { // Remove tx ouputs created at or after rollback block @@ -170,23 +180,11 @@ impl UTxORegistry { #[cfg(test)] mod tests { use crate::utxo_registry::UTxORegistry; - use acropolis_common::{params::SECURITY_PARAMETER_K, TxHash, TxIdentifier, TxOutRef}; - use anyhow::Result; + use acropolis_common::{params::SECURITY_PARAMETER_K, TxHash, TxOutRef}; fn make_hash(byte: u8) -> TxHash { TxHash::new([byte; 32]) } - impl UTxORegistry { - /// Lookup unspent tx output - pub fn lookup_by_hash(&self, tx_ref: TxOutRef) -> Result { - self.live_map.get(&tx_ref).copied().ok_or_else(|| { - anyhow::anyhow!( - "TxHash not found or already spent: {:?}", - hex::encode(tx_ref.tx_hash) - ) - }) - } - } #[test] fn add_and_lookup() { diff --git a/modules/tx_unpacker/src/validations/mod.rs b/modules/tx_unpacker/src/validations/mod.rs new file mode 100644 index 00000000..d9f27afd --- /dev/null +++ b/modules/tx_unpacker/src/validations/mod.rs @@ -0,0 +1,2 @@ +pub mod shelley; +pub use self::shelley::*; diff --git a/modules/tx_unpacker/src/validations/shelley/mod.rs b/modules/tx_unpacker/src/validations/shelley/mod.rs new file mode 100644 index 00000000..96a8b318 --- /dev/null +++ b/modules/tx_unpacker/src/validations/shelley/mod.rs @@ -0,0 +1,23 @@ +use acropolis_common::{ + protocol_params::ShelleyParams, validation::TransactionValidationError, TxIdentifier, TxOutRef, +}; +use anyhow::Result; +use pallas::ledger::traverse::{self, MultiEraTx}; + +pub mod utxo; + +pub fn validate_shelley_tx( + raw_tx: &[u8], + shelley_params: &ShelleyParams, + current_slot: u64, + lookup_by_hash: F, +) -> Result<(), TransactionValidationError> +where + F: Fn(TxOutRef) -> Result, +{ + let tx = MultiEraTx::decode_for_era(traverse::Era::Shelley, raw_tx) + .map_err(|e| TransactionValidationError::CborDecodeError(e.to_string()))?; + utxo::validate_shelley_tx(&tx, shelley_params, current_slot, lookup_by_hash).map_err(|e| *e)?; + + Ok(()) +} diff --git a/modules/tx_unpacker/src/validations/shelley/utxo.rs b/modules/tx_unpacker/src/validations/shelley/utxo.rs new file mode 100644 index 00000000..a461307b --- /dev/null +++ b/modules/tx_unpacker/src/validations/shelley/utxo.rs @@ -0,0 +1,362 @@ +//! Shelley era transaction validation +//! Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L343 + +use acropolis_codec; +use acropolis_common::{ + protocol_params::ShelleyParams, validation::UTxOValidationError, Address, Era, Lovelace, + NetworkId, TxIdentifier, TxOutRef, +}; +use anyhow::Result; +use pallas::{ + codec as pallas_codec, + ledger::{ + addresses::Address as PallasAddress, + primitives::alonzo, + traverse::{Era as PallasEra, MultiEraTx}, + }, +}; +use tracing::error; + +fn get_lovelace_from_alonzo_value(val: &alonzo::Value) -> Lovelace { + match val { + alonzo::Value::Coin(res) => *res, + alonzo::Value::Multiasset(res, _) => *res, + } +} + +fn get_value_size_in_bytes(val: &alonzo::Value) -> u64 { + let mut buf = Vec::new(); + let _ = pallas_codec::minicbor::encode(val, &mut buf); + (buf.len() as u64).div_ceil(8) +} + +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/mary/impl/src/Cardano/Ledger/Mary/TxOut.hs#L52 +fn compute_min_lovelace(value: &alonzo::Value, shelley_params: &ShelleyParams) -> Lovelace { + match value { + alonzo::Value::Coin(_) => shelley_params.protocol_params.min_utxo_value, + alonzo::Value::Multiasset(lovelace, _) => { + let utxo_entry_size = 27 + get_value_size_in_bytes(value); + let coins_per_utxo_word = shelley_params.protocol_params.min_utxo_value / 27; + (*lovelace).max(coins_per_utxo_word * utxo_entry_size) + } + } +} + +pub type UTxOValidationResult = Result<(), Box>; + +pub fn validate_shelley_tx( + tx: &MultiEraTx, + shelley_params: &ShelleyParams, + current_slot: u64, + lookup_by_hash: F, +) -> UTxOValidationResult +where + F: Fn(TxOutRef) -> Result, +{ + let network_id = shelley_params.network_id; + let tx_size = tx.size() as u32; + + let mtx = match tx { + MultiEraTx::AlonzoCompatible(mtx, PallasEra::Shelley) => mtx, + _ => { + error!("Not a Shelley transaction: {:?}", tx); + return Err(Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: "Not a Shelley transaction".to_string(), + })); + } + }; + let transaction_body = &mtx.transaction_body; + + validate_time_to_live(mtx, current_slot)?; + validate_input_set_empty_utxo(transaction_body)?; + validate_fee_too_small_utxo(transaction_body, tx_size, shelley_params)?; + validate_bad_inputs_utxo(transaction_body, lookup_by_hash)?; + validate_wrong_network(transaction_body, network_id)?; + validate_wrong_network_withdrawal(transaction_body, network_id)?; + validate_output_too_small_utxo(transaction_body, shelley_params)?; + validate_max_tx_size_utxo(tx_size, shelley_params)?; + Ok(()) +} + +/// Validate transaction's TTL field +/// pass if ttl >= current_slot +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L421 +pub fn validate_time_to_live(tx: &alonzo::MintedTx, current_slot: u64) -> UTxOValidationResult { + if let Some(ttl) = tx.transaction_body.ttl { + if ttl >= current_slot { + Ok(()) + } else { + Err(Box::new(UTxOValidationError::ExpiredUTxO { + ttl, + current_slot, + })) + } + } else { + Err(Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: "TTL is missing".to_string(), + })) + } +} + +/// Validate every transaction must consume at least one UTxO +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L435 +pub fn validate_input_set_empty_utxo( + transaction_body: &alonzo::TransactionBody, +) -> UTxOValidationResult { + if transaction_body.inputs.is_empty() { + Err(Box::new(UTxOValidationError::InputSetEmptyUTxO)) + } else { + Ok(()) + } +} + +/// Validate every transaction has minimum fee required +/// Fee calculation: +/// minFee = (tx_size_in_bytes * min_a) + min_b + ref_script_fee (this is after Alonzo Era) +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L447 +pub fn validate_fee_too_small_utxo( + transaction_body: &alonzo::TransactionBody, + tx_size: u32, + shelley_params: &ShelleyParams, +) -> UTxOValidationResult { + let min_fee = shelley_params.min_fee(tx_size); + if transaction_body.fee < min_fee { + Err(Box::new(UTxOValidationError::FeeTooSmallUTxO { + supplied: transaction_body.fee, + required: min_fee, + })) + } else { + Ok(()) + } +} + +/// Validate every transaction's input exists in the current UTxO set. +/// This prevents double spending. +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L468 +pub fn validate_bad_inputs_utxo( + transaction_body: &alonzo::TransactionBody, + lookup_by_hash: F, +) -> UTxOValidationResult +where + F: Fn(TxOutRef) -> Result, +{ + for (index, input) in transaction_body.inputs.iter().enumerate() { + let tx_ref = TxOutRef::new((*input.transaction_id).into(), input.index as u16); + if lookup_by_hash(tx_ref).is_err() { + return Err(Box::new(UTxOValidationError::BadInputsUTxO { + bad_input: tx_ref, + bad_input_index: index, + })); + } + } + Ok(()) +} + +/// Validate every output address match the network +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L481 +pub fn validate_wrong_network( + transaction_body: &alonzo::TransactionBody, + network_id: NetworkId, +) -> UTxOValidationResult { + for (index, output) in transaction_body.outputs.iter().enumerate() { + let pallas_address = PallasAddress::from_bytes(output.address.as_ref()).map_err(|_| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Malformed address at output {index}"), + }) + })?; + + let address = + acropolis_codec::map_parameters::map_address(&pallas_address).map_err(|e| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Invalid address at output {index}: {}", e), + }) + })?; + + let is_network_correct = match &address { + // NOTE: + // need to parse byron address's attributes and get network magic + Address::Byron(_) => true, + Address::Shelley(shelley_address) => shelley_address.network == network_id, + _ => { + return Err(Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Not a Shelley Address at output {index}"), + })) + } + }; + if !is_network_correct { + return Err(Box::new(UTxOValidationError::WrongNetwork { + expected: network_id, + wrong_address: address, + output_index: index, + })); + } + } + + Ok(()) +} + +/// Validate every withdrawal account addresses match the network +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L497 +pub fn validate_wrong_network_withdrawal( + transaction_body: &alonzo::TransactionBody, + network_id: NetworkId, +) -> UTxOValidationResult { + let Some(withdrawals) = transaction_body.withdrawals.as_ref() else { + return Ok(()); + }; + for (index, (stake_address_bytes, _)) in withdrawals.iter().enumerate() { + let pallas_reward_adddess = + PallasAddress::from_bytes(stake_address_bytes).map_err(|_| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Malformed reward address at withdrawal {index}"), + }) + })?; + + let stake_address = acropolis_codec::map_parameters::map_address(&pallas_reward_adddess) + .map_err(|e| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Invalid reward address at withdrawal {index}: {}", e), + }) + })?; + + let stake_address = match stake_address { + Address::Stake(stake_address) => stake_address, + _ => { + return Err(Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Not a Stake Address at withdrawal {index}"), + })); + } + }; + + if stake_address.network != network_id { + return Err(Box::new(UTxOValidationError::WrongNetworkWithdrawal { + expected: network_id, + wrong_account: stake_address, + withdrawal_index: index, + })); + } + } + + Ok(()) +} + +/// Validate every output has minimum required lovelace +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L531 +pub fn validate_output_too_small_utxo( + transaction_body: &alonzo::TransactionBody, + shelley_params: &ShelleyParams, +) -> UTxOValidationResult { + for (index, output) in transaction_body.outputs.iter().enumerate() { + let lovelace = get_lovelace_from_alonzo_value(&output.amount); + let required_lovelace = compute_min_lovelace(&output.amount, shelley_params); + if lovelace < required_lovelace { + return Err(Box::new(UTxOValidationError::OutputTooSmallUTxO { + output_index: index, + lovelace, + required_lovelace, + })); + } + } + Ok(()) +} + +/// Validate transaction size is under the limit +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L575 +pub fn validate_max_tx_size_utxo( + tx_size: u32, + shelley_params: &ShelleyParams, +) -> UTxOValidationResult { + let max_tx_size = shelley_params.protocol_params.max_tx_size; + if tx_size > max_tx_size { + Err(Box::new(UTxOValidationError::MaxTxSizeUTxO { + supplied: tx_size, + max: max_tx_size, + })) + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_utils::TestContext, validation_fixture}; + use acropolis_common::{ShelleyAddress, StakeAddress, TxHash}; + use pallas::ledger::traverse; + use std::str::FromStr; + use test_case::test_case; + + #[test_case(validation_fixture!("cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f") => + matches Ok(()); + "valid transaction 1" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e") => + matches Ok(()); + "valid transaction 2" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "expired_utxo") => + matches Err(UTxOValidationError::ExpiredUTxO { ttl: 7084747, current_slot: 7084748 }); + "expired_utxo" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "input_set_empty_utxo") => + matches Err(UTxOValidationError::InputSetEmptyUTxO); + "input_set_empty_utxo" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "fee_too_small_utxo") => + matches Err(UTxOValidationError::FeeTooSmallUTxO { supplied: 22541, required: 172277 }); + "fee_too_small_utxo" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "bad_inputs_utxo") => + matches Err(UTxOValidationError::BadInputsUTxO { bad_input, bad_input_index }) + if bad_input == TxOutRef::new(TxHash::from_str("d93625fb30376a1eaf90e6232296b0a31b7e63fac2af01381ffe58a574aae537").unwrap(), 1) && bad_input_index == 0; + "bad_inputs_utxo" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "wrong_network") => + matches Err(UTxOValidationError::WrongNetwork { expected: NetworkId::Mainnet, wrong_address, output_index }) + if wrong_address == Address::Shelley(ShelleyAddress::from_string("addr_test1qzvsy7ftzmrqj3hfs6ppczx263rups3fy3q0z0msnfw2e7s663nkrm3jz3sre0aupn4mdmdz8tdakdhgppaz58qkwe0q680lcj").unwrap()) + && output_index == 1; + "wrong_network" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "output_too_small_utxo") => + matches Err(UTxOValidationError::OutputTooSmallUTxO { output_index: 1, lovelace: 1, required_lovelace: 1000000 }); + "output_too_small_utxo" + )] + #[test_case(validation_fixture!("20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e", "max_tx_size_utxo") => + matches Err(UTxOValidationError::MaxTxSizeUTxO { supplied: 17983, max: 16384 }); + "max_tx_size_utxo" + )] + /// This tx contains withdrawal + #[test_case(validation_fixture!("a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a") => + matches Ok(()); + "valid transaction 3 with withdrawal" + )] + #[test_case(validation_fixture!("a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a", "wrong_network_withdrawal") => + matches Err(UTxOValidationError::WrongNetworkWithdrawal { expected: NetworkId::Mainnet, wrong_account, withdrawal_index }) + if wrong_account == StakeAddress::from_string("stake_test1upfe3tuzexk65edjy8t4dsfjcs2scyhwwucwkf7qmmg3mmqx3st08").unwrap() + && withdrawal_index == 0; + "wrong_network_withdrawal" + )] + #[allow(clippy::result_large_err)] + fn shelley_test((ctx, raw_tx): (TestContext, Vec)) -> Result<(), UTxOValidationError> { + let tx = MultiEraTx::decode_for_era(traverse::Era::Shelley, &raw_tx).unwrap(); + + let lookup_by_hash = |tx_ref: TxOutRef| -> Result { + ctx.utxos.get(&tx_ref).copied().ok_or_else(|| { + anyhow::anyhow!( + "TxHash not found or already spent: {:?}", + hex::encode(tx_ref.tx_hash) + ) + }) + }; + validate_shelley_tx(&tx, &ctx.shelley_params, ctx.current_slot, lookup_by_hash) + .map_err(|e| *e) + } +} diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/bad_inputs_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/bad_inputs_utxo.cbor new file mode 100644 index 00000000..3fdaddaf --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/bad_inputs_utxo.cbor @@ -0,0 +1 @@ +84a40081825820d93625fb30376a1eaf90e6232296b0a31b7e63fac2af01381ffe58a574aae53701018282584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40021a0002a1fd031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/context.json b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/context.json new file mode 100644 index 00000000..cd0e18fa --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/context.json @@ -0,0 +1,47 @@ +{ + "current_slot": 7084748, + "shelley_params": { + "activeSlotsCoeff": 0.05, + "epochLength": 432000, + "maxKesEvolutions": 62, + "maxLovelaceSupply": 45000000000000000, + "networkId": "Mainnet", + "networkMagic": 764824073, + "protocolParams": { + "protocolVersion": { + "minor": 0, + "major": 2 + }, + "maxTxSize": 16384, + "maxBlockBodySize": 65536, + "maxBlockHeaderSize": 1100, + "keyDeposit": 2000000, + "minUTxOValue": 1000000, + "minFeeA": 44, + "minFeeB": 155381, + "poolDeposit": 500000000, + "nOpt": 150, + "minPoolCost": 340000000, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "decentralisationParam": 1, + "rho": 0.003, + "tau": 0.2, + "a0": 0.3 + }, + "securityParam": 2160, + "slotLength": 1, + "slotsPerKesPeriod": 129600, + "systemStart": "2017-09-23T21:44:51Z", + "updateQuorum": 5, + "genDelegs": {} + }, + "utxos": [ + [ + ["278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e", 1], + [4616843, 0] + ] + ] +} diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/expired_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/expired_utxo.cbor new file mode 100644 index 00000000..34520c9e --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/expired_utxo.cbor @@ -0,0 +1 @@ +84A40081825820278EC9A3DFB551288AFFD8D84B2D6C70B56A153AD1FD43E7B18A5528F687733E01018282584C82D818584283581CE8BD78295559F7EDD927190EC36F7FBE438F74CF0C9380C717AEB56FA101581E581C2B0B011BA3683D490846FD2A91BF44F73A59B1F93E5E753FC1AEBED1001A6363D6C21B000000BF32F3680F825839019902792B16C60946E986821C08CAD447C0C2292440F13F709A5CACFA1AD46761EE3214603CBFBC0CEBB6EDA23ADBDB36E8087A2A1C16765E1B0000000BA4324C40021A0002A1FD031A006C1ACBA1028184582041A0B7A66D454ED0465E7B601D1FF1FE3CDDCDF5D127D1DA108CBB3555747CD158404C832202E28859CEFD3169EF238963A887A396E7773519B68721F9C059ABF7BEC56422111F4C66B13A116C2D10329A289C815FCE6D03F6BE903E74C3959BC703582066810329C45D09FD3E396A4EB1FF5E9D4632D5E156B5B320AD992C34D67E25F75822A101581E581C2B0B011BA3683D07B5F91D2AB76A8CAAB0E4DDA6099218E20432E4CCF5F6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/fee_too_small_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/fee_too_small_utxo.cbor new file mode 100644 index 00000000..270d8ca9 --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/fee_too_small_utxo.cbor @@ -0,0 +1 @@ +84a40081825820278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e01018282584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c400219580d031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/input_set_empty_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/input_set_empty_utxo.cbor new file mode 100644 index 00000000..ec56ce5c --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/input_set_empty_utxo.cbor @@ -0,0 +1 @@ +84A40080018282584C82D818584283581CE8BD78295559F7EDD927190EC36F7FBE438F74CF0C9380C717AEB56FA101581E581C2B0B011BA3683D490846FD2A91BF44F73A59B1F93E5E753FC1AEBED1001A6363D6C21B000000BF32F3680F825839019902792B16C60946E986821C08CAD447C0C2292440F13F709A5CACFA1AD46761EE3214603CBFBC0CEBB6EDA23ADBDB36E8087A2A1C16765E1B0000000BA4324C40021A0002A1FD031A006C36C5A1028184582041A0B7A66D454ED0465E7B601D1FF1FE3CDDCDF5D127D1DA108CBB3555747CD158404C832202E28859CEFD3169EF238963A887A396E7773519B68721F9C059ABF7BEC56422111F4C66B13A116C2D10329A289C815FCE6D03F6BE903E74C3959BC703582066810329C45D09FD3E396A4EB1FF5E9D4632D5E156B5B320AD992C34D67E25F75822A101581E581C2B0B011BA3683D07B5F91D2AB76A8CAAB0E4DDA6099218E20432E4CCF5F6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/max_tx_size_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/max_tx_size_utxo.cbor new file mode 100644 index 00000000..13363dd0 --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/max_tx_size_utxo.cbor @@ -0,0 +1 @@ +84a40081825820278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e010199010182584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40021a0011e43d031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/output_too_small_utxo.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/output_too_small_utxo.cbor new file mode 100644 index 00000000..6cf3d2c1 --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/output_too_small_utxo.cbor @@ -0,0 +1 @@ +84a40081825820278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e01018282584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e01021a0002a1fd031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/tx.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/tx.cbor new file mode 100644 index 00000000..11eddb3a --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/tx.cbor @@ -0,0 +1 @@ +84a40081825820278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e01018282584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839019902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40021a0002a1fd031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/wrong_network.cbor b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/wrong_network.cbor new file mode 100644 index 00000000..bb0bf133 --- /dev/null +++ b/modules/tx_unpacker/tests/data/20ded0bfef32fc5eefba2c1f43bcd99acc0b1c3284617c3cb355ad0eadccaa6e/wrong_network.cbor @@ -0,0 +1 @@ +84a40081825820278ec9a3dfb551288affd8d84b2d6c70b56a153ad1fd43e7b18a5528f687733e01018282584c82d818584283581ce8bd78295559f7edd927190ec36f7fbe438f74cf0c9380c717aeb56fa101581e581c2b0b011ba3683d490846fd2a91bf44f73a59b1f93e5e753fc1aebed1001a6363d6c21b000000bf32f3680f825839009902792b16c60946e986821c08cad447c0c2292440f13f709a5cacfa1ad46761ee3214603cbfbc0cebb6eda23adbdb36e8087a2a1c16765e1b0000000ba4324c40021a0002a1fd031a006c36c5a1028184582041a0b7a66d454ed0465e7b601d1ff1fe3cddcdf5d127d1da108cbb3555747cd158404c832202e28859cefd3169ef238963a887a396e7773519b68721f9c059abf7bec56422111f4c66b13a116c2d10329a289c815fce6d03f6be903e74c3959bc703582066810329c45d09fd3e396a4eb1ff5e9d4632d5e156b5b320ad992c34d67e25f75822a101581e581c2b0b011ba3683d07b5f91d2ab76a8caab0e4dda6099218e20432e4ccf5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/context.json b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/context.json new file mode 100644 index 00000000..7685bc3e --- /dev/null +++ b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/context.json @@ -0,0 +1,59 @@ +{ + "current_slot": 9244417, + "shelley_params": { + "activeSlotsCoeff": 0.05, + "epochLength": 432000, + "maxKesEvolutions": 62, + "maxLovelaceSupply": 45000000000000000, + "networkId": "Mainnet", + "networkMagic": 764824073, + "protocolParams": { + "protocolVersion": { + "minor": 0, + "major": 2 + }, + "maxTxSize": 16384, + "maxBlockBodySize": 65536, + "maxBlockHeaderSize": 1100, + "keyDeposit": 2000000, + "minUTxOValue": 1000000, + "minFeeA": 44, + "minFeeB": 155381, + "poolDeposit": 500000000, + "nOpt": 150, + "minPoolCost": 340000000, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "decentralisationParam": 1, + "rho": 0.003, + "tau": 0.2, + "a0": 0.3 + }, + "securityParam": 2160, + "slotLength": 1, + "slotsPerKesPeriod": 129600, + "systemStart": "2017-09-23T21:44:51Z", + "updateQuorum": 5, + "genDelegs": {} + }, + "utxos": [ + [ + ["0035edcf1ec947ee542330a8dad1f71c426af6958dd863ca4528cbfcff7b0d45", 0], + [4701984, 0] + ], + [ + ["2197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c00", 5], + [4491004, 0] + ], + [ + ["2197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c00", 11], + [4491004, 0] + ], + [ + ["d9ca0ba5ff5b5096fb7bbc2b967685db5c70c17bb0b5120aec03a0802b3f312d", 0], + [4530419, 0] + ] + ] +} diff --git a/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/tx.cbor b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/tx.cbor new file mode 100644 index 00000000..f8425c58 --- /dev/null +++ b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/tx.cbor @@ -0,0 +1 @@ +84a500848258200035edcf1ec947ee542330a8dad1f71c426af6958dd863ca4528cbfcff7b0d45008258202197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c00058258202197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c000b825820d9ca0ba5ff5b5096fb7bbc2b967685db5c70c17bb0b5120aec03a0802b3f312d000182825839013261ca1f486f7afb6b1983ab768e2ef5e9577306bfea57455989e1c65398af82c9adaa65b221d756c132c4150c12ee7730eb27c0ded11dec1b00000004ce8617058258390151a6fa6547ce05ce28f400414595a0f03062e979008d7f9e55f4756e318a09b650a8e0b01486e093d39b09d16b756cf39fb906e16a642f621b0000001fe5d61a00021a0002f139031a008d2b1905a1581de15398af82c9adaa65b221d756c132c4150c12ee7730eb27c0ded11dec1a12172fd1a10085825820b863a9893cce1d082882e18d2ae3ea7aa65c04b404630069edf25863980a3471584061641a36523eb8168e85633405dce2a70cf938a00d9d2a3d26b5ef8f44eb8fc4dd9fe759537c8d977d369c6db50328c1ee3fedf84abc811f8e6a42d7b1266c0c825820b7804a849bd78e16b0e4288a72f7ed53d8f1f5026a230e7a58b5324fd0ccf4b058403f132126e3f6166d578a55ea9978aeed195ea6317919f84df86f2ba9df8fb14664421a70aa1e011dd5e41374c8ff10b57a3341c68d8674b16b66666f7810030a8258203b76d38d9e2770dc2108408762be264be5ef9f733ae8e5f8073efc5b3b82de385840e546a07eba8cbdf78d75a364f7396baeb0c721f5a3b00fbb3216f3896d053dd169ca2dcde80f665e9cf15619c1a8808534bed84d16846d0b427bc2618700690f825820e78d0b5d0688f8074ad267246e406022c6e6ccd7a73fbb56bcbba3cccaa9a278584060647ed5d67a5cb2d9f51e7fbf95b6bef01699b0ad24a99eb0a882341286aadf36ee5bfcd19aa5918bfff34a39d8158d472d083fce21eb000a115e3732176102825820edc6836e44b94e4c9e01447ed2a55da3fdbc6f59a85b04bc684726bc9ad99d1658402656ad848fd0340be600519044b97fe2227387f19b0f6ebc70de756871b53b64a65c51c8e8bb3f599cdf1378675b6e0506ccc0a6339217684e784750c66e2c02f5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/wrong_network_withdrawal.cbor b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/wrong_network_withdrawal.cbor new file mode 100644 index 00000000..0e7998a8 --- /dev/null +++ b/modules/tx_unpacker/tests/data/a1aaa9c239f17e6feab5767f61457a3e6251cd0bb94a00a5d41847435caaa42a/wrong_network_withdrawal.cbor @@ -0,0 +1 @@ +84a500848258200035edcf1ec947ee542330a8dad1f71c426af6958dd863ca4528cbfcff7b0d45008258202197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c00058258202197beb48981c6bc0eed1f6f47da9910763a4be0450f249a3db5fa8fbaf70c000b825820d9ca0ba5ff5b5096fb7bbc2b967685db5c70c17bb0b5120aec03a0802b3f312d000182825839013261ca1f486f7afb6b1983ab768e2ef5e9577306bfea57455989e1c65398af82c9adaa65b221d756c132c4150c12ee7730eb27c0ded11dec1b00000004ce8617058258390151a6fa6547ce05ce28f400414595a0f03062e979008d7f9e55f4756e318a09b650a8e0b01486e093d39b09d16b756cf39fb906e16a642f621b0000001fe5d61a00021a0002f139031a008d2b1905a1581de05398af82c9adaa65b221d756c132c4150c12ee7730eb27c0ded11dec1a12172fd1a10085825820b863a9893cce1d082882e18d2ae3ea7aa65c04b404630069edf25863980a3471584061641a36523eb8168e85633405dce2a70cf938a00d9d2a3d26b5ef8f44eb8fc4dd9fe759537c8d977d369c6db50328c1ee3fedf84abc811f8e6a42d7b1266c0c825820b7804a849bd78e16b0e4288a72f7ed53d8f1f5026a230e7a58b5324fd0ccf4b058403f132126e3f6166d578a55ea9978aeed195ea6317919f84df86f2ba9df8fb14664421a70aa1e011dd5e41374c8ff10b57a3341c68d8674b16b66666f7810030a8258203b76d38d9e2770dc2108408762be264be5ef9f733ae8e5f8073efc5b3b82de385840e546a07eba8cbdf78d75a364f7396baeb0c721f5a3b00fbb3216f3896d053dd169ca2dcde80f665e9cf15619c1a8808534bed84d16846d0b427bc2618700690f825820e78d0b5d0688f8074ad267246e406022c6e6ccd7a73fbb56bcbba3cccaa9a278584060647ed5d67a5cb2d9f51e7fbf95b6bef01699b0ad24a99eb0a882341286aadf36ee5bfcd19aa5918bfff34a39d8158d472d083fce21eb000a115e3732176102825820edc6836e44b94e4c9e01447ed2a55da3fdbc6f59a85b04bc684726bc9ad99d1658402656ad848fd0340be600519044b97fe2227387f19b0f6ebc70de756871b53b64a65c51c8e8bb3f599cdf1378675b6e0506ccc0a6339217684e784750c66e2c02f5f6 \ No newline at end of file diff --git a/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/context.json b/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/context.json new file mode 100644 index 00000000..c5b010c6 --- /dev/null +++ b/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/context.json @@ -0,0 +1,47 @@ +{ + "current_slot": 4953800, + "shelley_params": { + "activeSlotsCoeff": 0.05, + "epochLength": 432000, + "maxKesEvolutions": 62, + "maxLovelaceSupply": 45000000000000000, + "networkId": "Mainnet", + "networkMagic": 764824073, + "protocolParams": { + "protocolVersion": { + "minor": 0, + "major": 2 + }, + "maxTxSize": 16384, + "maxBlockBodySize": 65536, + "maxBlockHeaderSize": 1100, + "keyDeposit": 2000000, + "minUTxOValue": 1000000, + "minFeeA": 44, + "minFeeB": 155381, + "poolDeposit": 500000000, + "nOpt": 150, + "minPoolCost": 340000000, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "decentralisationParam": 1, + "rho": 0.003, + "tau": 0.2, + "a0": 0.3 + }, + "securityParam": 2160, + "slotLength": 1, + "slotsPerKesPeriod": 129600, + "systemStart": "2017-09-23T21:44:51Z", + "updateQuorum": 5, + "genDelegs": {} + }, + "utxos": [ + [ + ["18ac56ec3b3495a9cab553c1589109c483784d2efeca1bc0c90a9218a1b5ed65", 1], + [3357485, 0] + ] + ] +} diff --git a/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/tx.cbor b/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/tx.cbor new file mode 100644 index 00000000..78dfa06d --- /dev/null +++ b/modules/tx_unpacker/tests/data/cd9037018278826d8ee2a80fe233862d0ff20bf61fc9f74543d682828c7cdb9f/tx.cbor @@ -0,0 +1 @@ +84a4008182582018ac56ec3b3495a9cab553c1589109c483784d2efeca1bc0c90a9218a1b5ed65010181825839015d8ddb84e4d2595d55c7e719c7810af21073e927b2e3a9d512201764092f2bf6629b431e22b3fe826edf4d0593c155ca3e760df29afc7ed31a00b00f5a021a00028de1031a004bb2d4a10281845820bd33384a2f1de2d86a9cea56d1cffbc47b4b7f1b6c11b148a21939c24922a85658406970a2bd458f527440c6d231be0f6c22140d3ad5c36a134aa536e2baed7fe327f90edab37676bed02813186773f3ab997c2af437802b2886636cc3bdbb38d40d5820981f1fdcd3166d2a8d4d70b613a16ed81c7fcf3ee70565c9dcffb8dad29c104b41a0f5f6 \ No newline at end of file