diff --git a/Cargo.lock b/Cargo.lock index 72a586e2..170e718b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,9 +81,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4973038846323e4e69a433916522195dce2947770076c03078fc21c80ea0f1c4" +checksum = "50ab0cd8afe573d1f7dc2353698a51b1f93aec362c8211e28cfd3948c6adba39" dependencies = [ "alloy-consensus", "alloy-contract", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" +checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" dependencies = [ "alloy-primitives", "num_enum", @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" dependencies = [ "alloy-consensus", "alloy-eips", @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7" +checksum = "7ac9e0c34dc6bce643b182049cdfcca1b8ce7d9c260cbdd561f511873b7e26cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -174,6 +174,7 @@ dependencies = [ "futures-util", "serde_json", "thiserror 2.0.18", + "tracing", ] [[package]] @@ -257,9 +258,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -276,14 +277,13 @@ dependencies = [ "serde", "serde_with", "sha2", - "thiserror 2.0.18", ] [[package]] name = "alloy-genesis" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" dependencies = [ "alloy-eips", "alloy-primitives", @@ -308,9 +308,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" dependencies = [ "alloy-consensus", "alloy-eips", @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" dependencies = [ "alloy-chains", "alloy-consensus", @@ -416,7 +416,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "thiserror 2.0.18", @@ -450,9 +450,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -460,7 +460,7 @@ dependencies = [ "alloy-transport-http", "futures", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "tokio", @@ -473,9 +473,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -517,9 +517,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" dependencies = [ "alloy-primitives", "serde", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" dependencies = [ "alloy-primitives", "async-trait", @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" dependencies = [ "alloy-consensus", "alloy-network", @@ -632,9 +632,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -655,14 +655,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde_json", "tower", "tracing", @@ -687,11 +687,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -1163,9 +1163,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -1473,19 +1473,20 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", @@ -1591,9 +1592,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -1737,9 +1738,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -2089,16 +2090,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -2123,21 +2114,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "serde", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -2147,6 +2123,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -2162,17 +2139,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" @@ -2221,7 +2187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2372,6 +2338,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dyn-eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388" + [[package]] name = "ecdsa" version = "0.16.9" @@ -3316,7 +3288,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -3634,14 +3605,15 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2 0.6.3", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", ] [[package]] @@ -3652,9 +3624,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -3695,9 +3667,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" @@ -3708,7 +3680,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -3717,9 +3689,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "jobserver" @@ -3733,10 +3727,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -3767,9 +3763,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3994,9 +3990,9 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.49.3" +version = "0.49.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cef64c3bdfaee9561319a289d778e9f8c56bd8e10f5d1059289ebb085ef09d7" +checksum = "a538e571cd38f504f761c61b8f79127489ea7a7d6f05c41ca15d31ffb5726326" dependencies = [ "async-channel", "asynchronous-codec", @@ -4524,9 +4520,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "bitflags", "libc", @@ -4685,9 +4681,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", @@ -4696,9 +4692,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -4919,9 +4915,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -5518,6 +5514,8 @@ dependencies = [ "cancellation", "chrono", "crossbeam", + "dyn-clone", + "dyn-eq", "futures", "hex", "libp2p", @@ -5525,6 +5523,7 @@ dependencies = [ "pluto-eth2api", "pluto-eth2util", "pluto-ssz", + "pluto-testutil", "prost 0.14.3", "prost-types 0.14.3", "rand 0.8.5", @@ -5538,6 +5537,7 @@ dependencies = [ "tokio-util", "tracing", "tree_hash", + "vise", "wiremock", ] @@ -5777,6 +5777,7 @@ dependencies = [ "hex", "k256", "pluto-crypto", + "pluto-eth2api", "rand 0.8.5", "rand_core 0.6.4", "thiserror 2.0.18", @@ -5898,7 +5899,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.5+spec-1.1.0", + "toml_edit 0.25.8+spec-1.1.0", ] [[package]] @@ -5957,9 +5958,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", @@ -6413,7 +6414,6 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -6421,8 +6421,6 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pki-types", "serde", "serde_json", @@ -6430,7 +6428,6 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", "tower", "tower-http", "tower-service", @@ -6438,7 +6435,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.6", ] [[package]] @@ -6580,9 +6576,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-hex" @@ -7088,9 +7084,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -7133,9 +7129,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -7697,9 +7693,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] @@ -7720,23 +7716,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.1+spec-1.1.0", + "toml_datetime 1.1.0+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] @@ -8023,9 +8019,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -8073,9 +8069,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64 0.22.1", "log", @@ -8083,14 +8079,14 @@ dependencies = [ "rustls", "rustls-pki-types", "ureq-proto", - "utf-8", + "utf8-zero", ] [[package]] name = "ureq-proto" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ "base64 0.22.1", "http", @@ -8112,10 +8108,10 @@ dependencies = [ ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "utf8-zero" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "utf8_iter" @@ -8131,9 +8127,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -8286,9 +8282,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" dependencies = [ "cfg-if", "once_cell", @@ -8299,23 +8295,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8323,9 +8315,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" dependencies = [ "bumpalo", "proc-macro2", @@ -8336,9 +8328,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" dependencies = [ "unicode-ident", ] @@ -8406,9 +8398,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837" dependencies = [ "js-sys", "wasm-bindgen", @@ -8662,15 +8654,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -8713,21 +8696,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -8776,12 +8744,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -8800,12 +8762,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -8824,12 +8780,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -8860,12 +8810,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -8884,12 +8828,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -8908,12 +8846,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -8932,12 +8864,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -8961,23 +8887,13 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wiremock" version = "0.6.5" @@ -9223,18 +9139,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 95b8387d..f0a08095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ cancellation = "0.1.0" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.5.53", features = ["derive", "env", "cargo"] } crossbeam = "0.8.4" +dyn-clone = "1.0" +dyn-eq = "0.1.3" either = "1.13" futures = "0.3" futures-timer = "3.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 2dc4dc1a..a387ba92 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -12,8 +12,11 @@ cancellation.workspace = true chrono.workspace = true crossbeam.workspace = true futures.workspace = true +dyn-clone.workspace = true +dyn-eq.workspace = true hex.workspace = true libp2p.workspace = true +vise.workspace = true pluto-eth2api.workspace = true prost.workspace = true prost-types.workspace = true @@ -38,6 +41,8 @@ prost-types.workspace = true hex.workspace = true chrono.workspace = true test-case.workspace = true +pluto-eth2util.workspace = true +pluto-testutil.workspace = true tokio = { workspace = true, features = ["test-util"] } wiremock.workspace = true pluto-ssz.workspace = true diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index d38cbdca..ac709968 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -22,3 +22,10 @@ pub mod version; /// Duty deadline tracking and notification. pub mod deadline; + +/// parsigdb +pub mod parsigdb; + +/// Test utilities. +#[cfg(test)] +pub mod testutils; diff --git a/crates/core/src/parasigdb/memory.rs b/crates/core/src/parsigdb/memory.rs similarity index 87% rename from crates/core/src/parasigdb/memory.rs rename to crates/core/src/parsigdb/memory.rs index 1a20e14e..e4d025e5 100644 --- a/crates/core/src/parasigdb/memory.rs +++ b/crates/core/src/parsigdb/memory.rs @@ -5,7 +5,8 @@ use tracing::{debug, warn}; use crate::{ deadline::Deadliner, - parasigdb::metrics::PARASIG_DB_METRICS, + parsigdb::metrics::PARSIG_DB_METRICS, + signeddata::SignedDataError, types::{Duty, DutyType, ParSignedData, ParSignedDataSet, PubKey}, }; use chrono::{DateTime, Utc}; @@ -55,9 +56,9 @@ pub type ThreshSub = Arc< /// Helper to create an internal subscriber from a closure. /// -/// The closure receives owned copies of the duty and data set. Since the closure -/// is `Fn` (can be called multiple times), you need to clone any captured Arc values -/// before the `async move` block. +/// The closure receives owned copies of the duty and data set. Since the +/// closure is `Fn` (can be called multiple times), you need to clone any +/// captured Arc values before the `async move` block. /// /// # Example /// ```ignore @@ -88,8 +89,8 @@ where /// Helper to create a threshold subscriber from a closure. /// /// The closure receives owned copies of the duty and data. Since the closure -/// is `Fn` (can be called multiple times), you need to clone any captured Arc values -/// before the `async move` block. +/// is `Fn` (can be called multiple times), you need to clone any captured Arc +/// values before the `async move` block. /// /// # Example /// ```ignore @@ -128,6 +129,10 @@ pub enum MemDBError { /// Share index of the mismatched signature share_idx: u64, }, + + /// Signed data error. + #[error("signed data error: {0}")] + SignedDataError(#[from] SignedDataError), } type Result = std::result::Result; @@ -186,8 +191,8 @@ impl MemDB { impl MemDB { /// Registers a subscriber for internally generated partial signed data. /// - /// The subscriber will be called when the node generates partial signed data - /// that needs to be exchanged with peers. + /// The subscriber will be called when the node generates partial signed + /// data that needs to be exchanged with peers. pub async fn subscribe_internal(&self, sub: InternalSub) -> Result<()> { let mut inner = self.inner.lock().await; inner.internal_subs.push(sub); @@ -204,11 +209,13 @@ impl MemDB { Ok(()) } - /// Stores internally generated partial signed data and notifies subscribers. + /// Stores internally generated partial signed data and notifies + /// subscribers. /// - /// This is called when the node generates partial signed data that needs to be - /// stored and exchanged with peers. It first stores the data (via `store_external`), - /// then calls all internal subscribers to trigger peer exchange. + /// This is called when the node generates partial signed data that needs to + /// be stored and exchanged with peers. It first stores the data (via + /// `store_external`), then calls all internal subscribers to trigger + /// peer exchange. pub async fn store_internal(&self, duty: &Duty, signed_set: &ParSignedDataSet) -> Result<()> { self.store_external(duty, signed_set).await?; @@ -226,9 +233,10 @@ impl MemDB { /// Stores externally received partial signed data and checks for threshold. /// - /// This is called when the node receives partial signed data from peers. It stores - /// the data, checks if enough matching signatures have been collected to meet the - /// threshold, and calls threshold subscribers when the threshold is reached. + /// This is called when the node receives partial signed data from peers. It + /// stores the data, checks if enough matching signatures have been + /// collected to meet the threshold, and calls threshold subscribers + /// when the threshold is reached. pub async fn store_external(&self, duty: &Duty, signed_data: &ParSignedDataSet) -> Result<()> { let _ = self.deadliner.add(duty.clone()).await; @@ -239,7 +247,7 @@ impl MemDB { .store( Key { duty: duty.clone(), - pub_key: pub_key.clone(), + pub_key: *pub_key, }, par_signed.clone(), ) @@ -257,7 +265,7 @@ impl MemDB { continue; }; - output.insert(pub_key.clone(), psigs); + output.insert(*pub_key, psigs); } if output.is_empty() { @@ -278,17 +286,15 @@ impl MemDB { /// Trims expired duties from the database. /// - /// This method runs in a loop, listening for expired duties from the deadliner - /// and removing their associated data from the database. It should be spawned - /// as a background task and will run until the cancellation token is triggered. + /// This method runs in a loop, listening for expired duties from the + /// deadliner and removing their associated data from the database. It + /// should be spawned as a background task and will run until the + /// cancellation token is triggered. pub async fn trim(&self) { - let deadliner_rx = self.deadliner.c(); - if deadliner_rx.is_none() { + let Some(mut deadliner_rx) = self.deadliner.c() else { warn!("Deadliner channel is not available"); return; - } - - let mut deadliner_rx = deadliner_rx.unwrap(); + }; loop { tokio::select! { @@ -345,14 +351,10 @@ impl MemDB { .push(k.clone()); if k.duty.duty_type == DutyType::Exit { - PARASIG_DB_METRICS.exit_total[&k.pub_key.to_string()].inc(); + PARSIG_DB_METRICS.exit_total[&k.pub_key.to_string()].inc(); } - let result = inner - .entries - .get(&k) - .map(|entries| entries.clone()) - .unwrap_or_default(); + let result = inner.entries.get(&k).cloned().unwrap_or_default(); Ok(Some(result)) } @@ -381,11 +383,11 @@ async fn get_threshold_matching( let mut sigs_by_msg_root: HashMap<[u8; 32], Vec> = HashMap::new(); for sig in sigs { - let root = sig.signed_data.message_root(); - sigs_by_msg_root - .entry(root) - .or_insert_with(Vec::new) - .push(sig.clone()); + let root = sig + .signed_data + .message_root() + .map_err(MemDBError::SignedDataError)?; + sigs_by_msg_root.entry(root).or_default().push(sig.clone()); } // Return the first set that has exactly threshold number of signatures diff --git a/crates/core/src/parsigdb/memory_internal_test.rs b/crates/core/src/parsigdb/memory_internal_test.rs new file mode 100644 index 00000000..58f4c474 --- /dev/null +++ b/crates/core/src/parsigdb/memory_internal_test.rs @@ -0,0 +1,215 @@ +use std::{ + sync::{Arc, Mutex as StdMutex}, + time::Duration, +}; + +use futures::future::{BoxFuture, FutureExt}; +use pluto_eth2api::{spec::altair, v1}; +use pluto_testutil as testutil; +use test_case::test_case; +use tokio::sync::{Mutex, mpsc}; +use tokio_util::sync::CancellationToken; + +use super::{MemDB, get_threshold_matching, threshold_subscriber}; +use crate::{ + deadline::Deadliner, + signeddata::{BeaconCommitteeSelection, SignedSyncMessage, VersionedAttestation}, + testutils::random_core_pub_key, + types::{Duty, DutyType, ParSignedData, ParSignedDataSet, SlotNumber}, +}; + +fn threshold(nodes: usize) -> u64 { + (2_u64 + .checked_mul(u64::try_from(nodes).expect("nodes overflow")) + .expect("nodes overflow")) + .div_ceil(3) +} + +#[test_case(Vec::new(), Vec::new() ; "empty")] +#[test_case(vec![0, 0, 0], vec![0, 1, 2] ; "all identical exact threshold")] +#[test_case(vec![0, 0, 0, 0], Vec::new() ; "all identical above threshold")] +#[test_case(vec![0, 0, 1, 0], vec![0, 1, 3] ; "one odd")] +#[test_case(vec![0, 0, 1, 1], Vec::new() ; "two odd")] +#[tokio::test] +async fn test_get_threshold_matching(input: Vec, output: Vec) { + const N: usize = 4; + + let slot = testutil::random_slot(); + let validator_index = testutil::random_v_idx(); + let roots = [testutil::random_root_bytes(), testutil::random_root_bytes()]; + let threshold = threshold(N); + + type Providers<'a> = [(&'a str, Box ParSignedData + 'a>); 2]; + + let providers: Providers<'_> = [ + ( + "sync_committee_message", + Box::new(|i| { + let message = altair::SyncCommitteeMessage { + slot, + beacon_block_root: roots[input[i]], + validator_index, + signature: testutil::random_eth2_signature_bytes(), + }; + + SignedSyncMessage::new_partial(message, u64::try_from(i.wrapping_add(1)).unwrap()) + }), + ), + ( + "selection", + Box::new(|i| { + let selection = v1::BeaconCommitteeSelection { + validator_index, + slot: u64::try_from(input[i]).unwrap(), + selection_proof: testutil::random_eth2_signature_bytes(), + }; + + BeaconCommitteeSelection::new_partial( + selection, + u64::try_from(i.wrapping_add(1)).unwrap(), + ) + }), + ), + ]; + + for (name, provider) in providers { + let mut data = Vec::new(); + for i in 0..input.len() { + data.push(provider(i)); + } + + let out = get_threshold_matching(&DutyType::SyncMessage, &data, threshold) + .await + .expect("threshold matching should succeed"); + let expect: Vec<_> = output.iter().map(|idx| data[*idx].clone()).collect(); + let expected_out = if expect.is_empty() { + None + } else { + Some(expect.clone()) + }; + + assert_eq!(expected_out, out, "{name}/output mismatch"); + assert_eq!( + out.as_ref() + .map(|matches| u64::try_from(matches.len()).unwrap() == threshold) + .unwrap_or(false), + expect.len() as u64 == threshold, + "{name}/ok mismatch" + ); + } +} + +#[tokio::test] +async fn test_memdb_threshold() { + const THRESHOLD: u64 = 7; + const N: usize = 10; + + let deadliner = Arc::new(TestDeadliner::new()); + let cancel = CancellationToken::new(); + let db = Arc::new(MemDB::new(cancel.clone(), THRESHOLD, deadliner.clone())); + + let trim_handle = tokio::spawn({ + let db = db.clone(); + async move { + db.trim().await; + } + }); + + let times_called = Arc::new(Mutex::new(0usize)); + db.subscribe_threshold(threshold_subscriber({ + let times_called = times_called.clone(); + move |_duty, _data| { + let times_called = times_called.clone(); + async move { + *times_called.lock().await += 1; + Ok(()) + } + } + })) + .await + .expect("subscription should succeed"); + + let pubkey = random_core_pub_key(); + let attestation = testutil::random_deneb_versioned_attestation(); + let duty = Duty::new_attester_duty(SlotNumber::new(123)); + + let enqueue_n = || async { + for i in 0..N { + let partial = VersionedAttestation::new_partial( + attestation.clone(), + u64::try_from(i + 1).unwrap(), + ) + .expect("versioned attestation should be valid"); + + let mut set = ParSignedDataSet::new(); + set.insert(pubkey, partial); + + db.store_external(&duty, &set) + .await + .expect("store_external should succeed"); + } + }; + + enqueue_n().await; + assert_eq!(1, *times_called.lock().await); + + deadliner.expire().await; + tokio::time::sleep(Duration::from_millis(20)).await; + + enqueue_n().await; + assert_eq!(2, *times_called.lock().await); + + cancel.cancel(); + trim_handle + .await + .expect("trim task should shut down cleanly"); +} + +struct TestDeadliner { + added: StdMutex>, + tx: mpsc::Sender, + rx: StdMutex>>, +} + +impl TestDeadliner { + fn new() -> Self { + let (tx, rx) = mpsc::channel(32); + Self { + added: StdMutex::new(Vec::new()), + tx, + rx: StdMutex::new(Some(rx)), + } + } + + async fn expire(&self) -> bool { + let duties = { + let mut added = self.added.lock().expect("test deadliner lock poisoned"); + std::mem::take(&mut *added) + }; + + for duty in duties { + if self.tx.send(duty).await.is_err() { + return false; + } + } + + true + } +} + +impl Deadliner for TestDeadliner { + fn add(&self, duty: Duty) -> BoxFuture<'_, bool> { + async move { + self.added + .lock() + .expect("test deadliner lock poisoned") + .push(duty); + true + } + .boxed() + } + + fn c(&self) -> Option> { + self.rx.lock().expect("test deadliner lock poisoned").take() + } +} diff --git a/crates/core/src/parsigdb/metrics.rs b/crates/core/src/parsigdb/metrics.rs new file mode 100644 index 00000000..ea93dcbe --- /dev/null +++ b/crates/core/src/parsigdb/metrics.rs @@ -0,0 +1,13 @@ +use vise::*; + +/// Metrics for the ParSigDB. +#[derive(Debug, Clone, Metrics)] +pub struct ParsigDBMetrics { + /// Total number of partially signed voluntary exits per public key + #[metrics(labels = ["pubkey"])] + pub exit_total: LabeledFamily, +} + +/// Global metrics for the ParSigDB. +#[vise::register] +pub static PARSIG_DB_METRICS: Global = Global::new(); diff --git a/crates/core/src/parsigdb/mod.rs b/crates/core/src/parsigdb/mod.rs new file mode 100644 index 00000000..fd01b279 --- /dev/null +++ b/crates/core/src/parsigdb/mod.rs @@ -0,0 +1,5 @@ +/// Memory implementation of the ParSigDB. +pub mod memory; + +/// Metrics for the ParSigDB. +pub mod metrics; diff --git a/crates/core/src/signeddata.rs b/crates/core/src/signeddata.rs index ab8247f2..56ef8622 100644 --- a/crates/core/src/signeddata.rs +++ b/crates/core/src/signeddata.rs @@ -48,6 +48,9 @@ pub enum SignedDataError { /// Invalid attestation wrapper JSON. #[error("unmarshal attestation")] AttestationJson, + /// Custom error. + #[error("{0}")] + Custom(Box), } fn hash_root(value: &T) -> [u8; 32] { @@ -131,23 +134,21 @@ impl Signature { } /// Creates a partially signed signature wrapper. - pub fn new_partial(sig: Self, share_idx: u64) -> ParSignedData { + pub fn new_partial(sig: Self, share_idx: u64) -> ParSignedData { ParSignedData::new(sig, share_idx) } } impl SignedData for Signature { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(self.clone()) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { Ok(signature) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Err(SignedDataError::UnsupportedSignatureMessageRoot) } } @@ -183,7 +184,7 @@ impl VersionedSignedProposal { pub fn new_partial( proposal: versioned::VersionedSignedProposal, share_idx: u64, - ) -> Result, SignedDataError> { + ) -> Result { Ok(ParSignedData::new(Self::new(proposal)?, share_idx)) } @@ -226,7 +227,7 @@ impl VersionedSignedProposal { pub fn new_partial_from_blinded_proposal( proposal: versioned::VersionedSignedBlindedProposal, share_idx: u64, - ) -> Result, SignedDataError> { + ) -> Result { Ok(ParSignedData::new( Self::from_blinded_proposal(proposal)?, share_idx, @@ -235,9 +236,7 @@ impl VersionedSignedProposal { } impl SignedData for VersionedSignedProposal { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { let proposal = &self.0; if proposal.version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -245,7 +244,7 @@ impl SignedData for VersionedSignedProposal { Ok(sig_from_eth2(proposal.block.signature())) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); let proposal = &mut out.0; if proposal.version == versioned::DataVersion::Unknown { @@ -257,7 +256,7 @@ impl SignedData for VersionedSignedProposal { Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { let proposal = &self.0; if proposal.version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -379,25 +378,23 @@ impl Attestation { } /// Creates a partial signed attestation wrapper. - pub fn new_partial(attestation: phase0::Attestation, share_idx: u64) -> ParSignedData { + pub fn new_partial(attestation: phase0::Attestation, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(attestation), share_idx) } } impl SignedData for Attestation { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0.data)) } } @@ -428,7 +425,7 @@ impl VersionedAttestation { pub fn new_partial( attestation: versioned::VersionedAttestation, share_idx: u64, - ) -> Result, SignedDataError> { + ) -> Result { Ok(ParSignedData::new(Self::new(attestation)?, share_idx)) } @@ -448,9 +445,7 @@ impl VersionedAttestation { } impl SignedData for VersionedAttestation { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { let version = self.0.version; if version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -462,7 +457,7 @@ impl SignedData for VersionedAttestation { .ok_or(SignedDataError::MissingAttestation(version)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); let version = out.0.version; if version == versioned::DataVersion::Unknown { @@ -477,7 +472,7 @@ impl SignedData for VersionedAttestation { Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { let version = self.0.version; if version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -582,19 +577,17 @@ pub struct SignedVoluntaryExit( ); impl SignedData for SignedVoluntaryExit { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0.message)) } } @@ -606,7 +599,7 @@ impl SignedVoluntaryExit { } /// Creates a partially signed voluntary exit wrapper. - pub fn new_partial(exit: phase0::SignedVoluntaryExit, share_idx: u64) -> ParSignedData { + pub fn new_partial(exit: phase0::SignedVoluntaryExit, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(exit), share_idx) } } @@ -641,15 +634,13 @@ impl VersionedSignedValidatorRegistration { pub fn new_partial( registration: versioned::VersionedSignedValidatorRegistration, share_idx: u64, - ) -> Result, SignedDataError> { + ) -> Result { Ok(ParSignedData::new(Self::new(registration)?, share_idx)) } } impl SignedData for VersionedSignedValidatorRegistration { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { match self.0.version { versioned::BuilderVersion::V1 => self .0 @@ -661,7 +652,7 @@ impl SignedData for VersionedSignedValidatorRegistration { } } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); match out.0.version { versioned::BuilderVersion::V1 => { @@ -678,7 +669,7 @@ impl SignedData for VersionedSignedValidatorRegistration { Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { match self.0.version { versioned::BuilderVersion::V1 => { let Some(v1) = self.0.v1.as_ref() else { @@ -746,19 +737,17 @@ pub struct SignedRandao( ); impl SignedData for SignedRandao { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0)) } } @@ -777,7 +766,7 @@ impl SignedRandao { epoch: phase0::Epoch, randao: phase0::BLSSignature, share_idx: u64, - ) -> ParSignedData { + ) -> ParSignedData { ParSignedData::new(Self::new(epoch, randao), share_idx) } } @@ -791,19 +780,17 @@ pub struct BeaconCommitteeSelection( ); impl SignedData for BeaconCommitteeSelection { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.selection_proof)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.selection_proof = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0.slot)) } } @@ -815,10 +802,7 @@ impl BeaconCommitteeSelection { } /// Creates a partial beacon committee selection wrapper. - pub fn new_partial( - selection: v1::BeaconCommitteeSelection, - share_idx: u64, - ) -> ParSignedData { + pub fn new_partial(selection: v1::BeaconCommitteeSelection, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(selection), share_idx) } } @@ -832,19 +816,17 @@ pub struct SyncCommitteeSelection( ); impl SignedData for SyncCommitteeSelection { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.selection_proof)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.selection_proof = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { let data = altair::SyncAggregatorSelectionData { slot: self.0.slot, subcommittee_index: self.0.subcommittee_index, @@ -861,10 +843,7 @@ impl SyncCommitteeSelection { } /// Creates a partial sync committee selection wrapper. - pub fn new_partial( - selection: v1::SyncCommitteeSelection, - share_idx: u64, - ) -> ParSignedData { + pub fn new_partial(selection: v1::SyncCommitteeSelection, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(selection), share_idx) } } @@ -878,19 +857,17 @@ pub struct SignedAggregateAndProof( ); impl SignedData for SignedAggregateAndProof { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0.message)) } } @@ -902,10 +879,7 @@ impl SignedAggregateAndProof { } /// Creates a partial signed aggregate-and-proof wrapper. - pub fn new_partial( - data: phase0::SignedAggregateAndProof, - share_idx: u64, - ) -> ParSignedData { + pub fn new_partial(data: phase0::SignedAggregateAndProof, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(data), share_idx) } } @@ -945,15 +919,13 @@ impl VersionedSignedAggregateAndProof { pub fn new_partial( data: versioned::VersionedSignedAggregateAndProof, share_idx: u64, - ) -> ParSignedData { + ) -> ParSignedData { ParSignedData::new(Self::new(data), share_idx) } } impl SignedData for VersionedSignedAggregateAndProof { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { let version = self.0.version; if version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -962,7 +934,7 @@ impl SignedData for VersionedSignedAggregateAndProof { Ok(sig_from_eth2(self.0.aggregate_and_proof.signature())) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); let version = out.0.version; if version == versioned::DataVersion::Unknown { @@ -975,7 +947,7 @@ impl SignedData for VersionedSignedAggregateAndProof { Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { let version = self.0.version; if version == versioned::DataVersion::Unknown { return Err(SignedDataError::UnknownVersion); @@ -1063,19 +1035,17 @@ pub struct SignedSyncMessage( ); impl SignedData for SignedSyncMessage { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(self.0.beacon_block_root) } } @@ -1087,7 +1057,7 @@ impl SignedSyncMessage { } /// Creates a partial signed sync committee message wrapper. - pub fn new_partial(data: altair::SyncCommitteeMessage, share_idx: u64) -> ParSignedData { + pub fn new_partial(data: altair::SyncCommitteeMessage, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(data), share_idx) } } @@ -1101,19 +1071,17 @@ pub struct SyncContributionAndProof( ); impl SignedData for SyncContributionAndProof { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.selection_proof)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.selection_proof = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { let data = altair::SyncAggregatorSelectionData { slot: self.0.contribution.slot, subcommittee_index: self.0.contribution.subcommittee_index, @@ -1130,7 +1098,7 @@ impl SyncContributionAndProof { } /// Creates a partial sync contribution-and-proof wrapper. - pub fn new_partial(proof: altair::ContributionAndProof, share_idx: u64) -> ParSignedData { + pub fn new_partial(proof: altair::ContributionAndProof, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(proof), share_idx) } } @@ -1144,19 +1112,17 @@ pub struct SignedSyncContributionAndProof( ); impl SignedData for SignedSyncContributionAndProof { - type Error = SignedDataError; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(sig_from_eth2(self.0.signature)) } - fn set_signature(&self, signature: Signature) -> Result { + fn set_signature(&self, signature: Signature) -> Result { let mut out = self.clone(); out.0.signature = sig_to_eth2(&signature); Ok(out) } - fn message_root(&self) -> Result<[u8; 32], Self::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok(hash_root(&self.0.message)) } } @@ -1168,10 +1134,7 @@ impl SignedSyncContributionAndProof { } /// Creates a partial signed sync contribution-and-proof wrapper. - pub fn new_partial( - proof: altair::SignedContributionAndProof, - share_idx: u64, - ) -> ParSignedData { + pub fn new_partial(proof: altair::SignedContributionAndProof, share_idx: u64) -> ParSignedData { ParSignedData::new(Self::new(proof), share_idx) } } @@ -2045,7 +2008,7 @@ mod tests { fn assert_set_signature(data: T) where - T: SignedData + std::fmt::Debug + PartialEq, + T: SignedData + std::fmt::Debug + PartialEq, { let clone = data.set_signature(sample_signature(0xAB)).unwrap(); let clone_sig = clone.signature().unwrap(); diff --git a/crates/core/src/testutils.rs b/crates/core/src/testutils.rs new file mode 100644 index 00000000..ae6b4f47 --- /dev/null +++ b/crates/core/src/testutils.rs @@ -0,0 +1,143 @@ +//! Test utilities for the Charon core. + +use rand::{Rng, SeedableRng}; + +use crate::types::PubKey; + +/// The size of a BLS public key in bytes. +const PK_LEN: usize = 48; + +/// Creates a new seeded random number generator. +/// +/// Returns a new random number generator seeded with a random value. +/// This matches the Go implementation: +/// `rand.New(rand.NewSource(rand.Int63()))`. +pub fn new_seed_rand() -> impl Rng { + let seed = rand::random::(); + rand::rngs::StdRng::seed_from_u64(seed) +} + +/// Returns a random core workflow pubkey. +/// +/// This is a convenience wrapper around `random_core_pub_key_seed` that creates +/// a new random seed for each call. +pub fn random_core_pub_key() -> PubKey { + random_core_pub_key_seed(new_seed_rand()) +} + +/// Returns a random core workflow pubkey using a provided random source. +/// +/// # Arguments +/// +/// * `rng` - A random number generator to use for generating the pubkey. +/// +/// # Panics +/// +/// Panics if the generated bytes cannot be converted to a valid PubKey. +/// This should never happen in practice as we generate exactly 48 bytes. +pub fn random_core_pub_key_seed(mut rng: R) -> PubKey { + let pubkey = deterministic_pub_key_seed(&mut rng); + PubKey::try_from(&pubkey[..]).expect("valid pubkey length") +} + +/// Generates a deterministic pubkey from a seeded RNG. +/// +/// This function creates a new RNG seeded from the input RNG, then fills +/// a 48-byte array with random data. This matches the Go implementation: +/// +/// ```go +/// random := rand.New(rand.NewSource(r.Int63())) +/// var key tbls.PublicKey +/// _, err := random.Read(key[:]) +/// ``` +/// +/// # Arguments +/// +/// * `rng` - A mutable reference to a random number generator. +/// +/// # Returns +/// +/// A 48-byte array containing random data suitable for use as a public key. +fn deterministic_pub_key_seed(rng: &mut R) -> [u8; PK_LEN] { + // Create a new RNG seeded from the input RNG (matching Go's + // rand.New(rand.NewSource(r.Int63()))) + let seed: u64 = rng.r#gen(); + let mut seeded_rng = rand::rngs::StdRng::seed_from_u64(seed); + + let mut key = [0u8; PK_LEN]; + // Fill the key with random bytes + for byte in &mut key { + *byte = seeded_rng.r#gen(); + } + + key +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_seed_rand_produces_different_values() { + let mut rng1 = new_seed_rand(); + let mut rng2 = new_seed_rand(); + + let val1: u64 = rng1.r#gen(); + let val2: u64 = rng2.r#gen(); + + // These should be different with very high probability + assert_ne!(val1, val2); + } + + #[test] + fn test_random_core_pub_key_generates_valid_keys() { + let pk1 = random_core_pub_key(); + let pk2 = random_core_pub_key(); + + // Keys should be different + assert_ne!(pk1, pk2); + + // Keys should have the correct length when serialized + assert_eq!(pk1.to_string().len(), 98); // 0x + 96 hex chars + assert_eq!(pk2.to_string().len(), 98); + } + + #[test] + fn test_random_core_pub_key_seed_is_deterministic() { + let seed = 12345u64; + let mut rng1 = rand::rngs::StdRng::seed_from_u64(seed); + let mut rng2 = rand::rngs::StdRng::seed_from_u64(seed); + + let pk1 = random_core_pub_key_seed(&mut rng1); + let pk2 = random_core_pub_key_seed(&mut rng2); + + // Same seed should produce same key + assert_eq!(pk1, pk2); + } + + #[test] + fn test_deterministic_pub_key_seed() { + let seed = 42u64; + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + + let key = deterministic_pub_key_seed(&mut rng); + + // Check that we got 48 bytes + assert_eq!(key.len(), PK_LEN); + + // Check that the key is not all zeros (very unlikely with a proper RNG) + assert!(key.iter().any(|&b| b != 0)); + } + + #[test] + fn test_random_core_pub_key_seed_different_rngs() { + let mut rng1 = rand::rngs::StdRng::seed_from_u64(1); + let mut rng2 = rand::rngs::StdRng::seed_from_u64(2); + + let pk1 = random_core_pub_key_seed(&mut rng1); + let pk2 = random_core_pub_key_seed(&mut rng2); + + // Different seeds should produce different keys + assert_ne!(pk1, pk2); + } +} diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 2d0f3b37..78e2bc62 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -3,9 +3,13 @@ use std::{collections::HashMap, fmt::Display, iter}; use chrono::{DateTime, Duration, Utc}; +use dyn_clone::DynClone; +use dyn_eq::DynEq; use serde::{Deserialize, Serialize}; use std::fmt::Debug as StdDebug; +use crate::signeddata::SignedDataError; + /// The type of duty. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -448,42 +452,64 @@ impl AsRef<[u8; SIG_LEN]> for Signature { } /// Signed data type -pub trait SignedData: Clone + Serialize + StdDebug { - /// The error type - type Error: std::error::Error; - +pub trait SignedData: DynClone + DynEq + StdDebug + Send + Sync { /// signature returns the signed duty data's signature. - fn signature(&self) -> Result; + fn signature(&self) -> Result; /// Returns a copy of signed duty data with the signature replaced. - fn set_signature(&self, signature: Signature) -> Result + fn set_signature(&self, signature: Signature) -> Result where Self: Sized; /// message_root returns the message root for the unsigned data. - fn message_root(&self) -> Result<[u8; 32], Self::Error>; + fn message_root(&self) -> Result<[u8; 32], SignedDataError>; } +dyn_eq::eq_trait_object!(SignedData); +dyn_clone::clone_trait_object!(SignedData); + // todo: add Eth2SignedData type // https://github.com/ObolNetwork/charon/blob/b3008103c5429b031b63518195f4c49db4e9a68d/core/types.go#L396 /// ParSignedData is a partially signed duty data only signed by a single /// threshold BLS share. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParSignedData { +#[derive(Debug)] +pub struct ParSignedData { /// Partially signed duty data. - pub signed_data: T, + pub signed_data: Box, /// Threshold BLS share index. pub share_idx: u64, } -impl ParSignedData -where - T: SignedData, -{ +impl Clone for ParSignedData { + fn clone(&self) -> Self { + Self { + signed_data: self.signed_data.clone(), + share_idx: self.share_idx, + } + } +} + +impl PartialEq for ParSignedData { + fn eq(&self, other: &Self) -> bool { + self.share_idx == other.share_idx && self.signed_data == other.signed_data + } +} + +impl Eq for ParSignedData {} + +impl ParSignedData { /// Create a new partially signed data. - pub fn new(partially_signed_data: T, share_idx: u64) -> Self { + pub fn new(partially_signed_data: T, share_idx: u64) -> Self { + Self { + signed_data: Box::new(partially_signed_data), + share_idx, + } + } + + /// Create a new partially signed data from a boxed signed data. + pub fn new_boxed(partially_signed_data: Box, share_idx: u64) -> Self { Self { signed_data: partially_signed_data, share_idx, @@ -493,49 +519,37 @@ where /// ParSignedDataSet is a set of partially signed duty data only signed by a /// single threshold BLS share. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParSignedDataSet(HashMap>); +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ParSignedDataSet(HashMap); -impl Default for ParSignedDataSet -where - T: SignedData, -{ - fn default() -> Self { - Self(HashMap::default()) - } -} - -impl ParSignedDataSet -where - T: SignedData, -{ +impl ParSignedDataSet { /// Create a new partially signed data set. pub fn new() -> Self { Self::default() } /// Get a partially signed data by public key. - pub fn get(&self, pub_key: &PubKey) -> Option<&ParSignedData> { + pub fn get(&self, pub_key: &PubKey) -> Option<&ParSignedData> { self.inner().get(pub_key) } /// Insert a partially signed data. - pub fn insert(&mut self, pub_key: PubKey, partially_signed_data: ParSignedData) { + pub fn insert(&mut self, pub_key: PubKey, partially_signed_data: ParSignedData) { self.inner_mut().insert(pub_key, partially_signed_data); } /// Remove a partially signed data by public key. - pub fn remove(&mut self, pub_key: &PubKey) -> Option> { + pub fn remove(&mut self, pub_key: &PubKey) -> Option { self.inner_mut().remove(pub_key) } /// Inner partially signed data set. - pub fn inner(&self) -> &HashMap> { + pub fn inner(&self) -> &HashMap { &self.0 } /// Inner partially signed data set. - pub fn inner_mut(&mut self) -> &mut HashMap> { + pub fn inner_mut(&mut self) -> &mut HashMap { &mut self.0 } } @@ -856,17 +870,15 @@ mod tests { struct MockSignedData; impl SignedData for MockSignedData { - type Error = std::io::Error; - - fn signature(&self) -> Result { + fn signature(&self) -> Result { Ok(Signature::new([42u8; SIG_LEN])) } - fn set_signature(&self, _signature: Signature) -> Result { + fn set_signature(&self, _signature: Signature) -> Result { Ok(self.clone()) } - fn message_root(&self) -> Result<[u8; 32], std::io::Error> { + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { Ok([42u8; 32]) } } @@ -874,13 +886,15 @@ mod tests { #[test] fn test_partially_signed_data_set() { let mut partially_signed_data_set = ParSignedDataSet::new(); - partially_signed_data_set.insert( - PubKey::new([42u8; PK_LEN]), - ParSignedData::new(MockSignedData, 0), - ); + let par_signed = ParSignedData::new(MockSignedData, 0); + partially_signed_data_set.insert(PubKey::new([42u8; PK_LEN]), par_signed.clone()); + let retrieved = partially_signed_data_set.get(&PubKey::new([42u8; PK_LEN])); + assert!(retrieved.is_some()); + let retrieved = retrieved.unwrap(); + assert_eq!(retrieved.share_idx, 0); assert_eq!( - partially_signed_data_set.get(&PubKey::new([42u8; PK_LEN])), - Some(&ParSignedData::new(MockSignedData, 0)) + retrieved.signed_data.signature().unwrap(), + Signature::new([42u8; SIG_LEN]) ); } diff --git a/crates/testutil/Cargo.toml b/crates/testutil/Cargo.toml index 0a973d1f..720bcc86 100644 --- a/crates/testutil/Cargo.toml +++ b/crates/testutil/Cargo.toml @@ -9,6 +9,7 @@ publish.workspace = true [dependencies] k256.workspace = true pluto-crypto.workspace = true +pluto-eth2api.workspace = true rand.workspace = true rand_core.workspace = true thiserror.workspace = true diff --git a/crates/testutil/src/lib.rs b/crates/testutil/src/lib.rs index abc00e7a..686c8c7a 100644 --- a/crates/testutil/src/lib.rs +++ b/crates/testutil/src/lib.rs @@ -6,3 +6,8 @@ /// Random utilities. pub mod random; + +pub use random::{ + random_deneb_versioned_attestation, random_eth2_signature, random_eth2_signature_bytes, + random_root, random_root_bytes, random_slot, random_v_idx, +}; diff --git a/crates/testutil/src/random.rs b/crates/testutil/src/random.rs index 65be74cc..8e4a8eeb 100644 --- a/crates/testutil/src/random.rs +++ b/crates/testutil/src/random.rs @@ -7,6 +7,14 @@ use k256::{ elliptic_curve::rand_core::{CryptoRng, Error, RngCore}, }; use pluto_crypto::{blst_impl::BlstImpl, tbls::Tbls, types::PrivateKey}; +use pluto_eth2api::{ + spec::phase0, + types::{ + AltairBeaconStateCurrentJustifiedCheckpoint, Data, + GetBlockAttestationsV2ResponseResponseDataArray2, + }, + versioned::{self, AttestationPayload}, +}; use rand::{Rng, SeedableRng, rngs::StdRng}; /// A deterministic RNG that always returns the same byte value. @@ -67,6 +75,26 @@ pub fn generate_test_bls_key(seed: u64) -> PrivateKey { .expect("deterministic key generation should not fail") } +/// Generates a random BLS signature as a hex string for testing. +/// +/// Returns a 96-byte (192 hex characters) BLS signature encoded as a hex string +/// with "0x" prefix. +pub fn random_eth2_signature() -> String { + let mut bytes = [0u8; 96]; + let mut rng = rand::thread_rng(); + for byte in &mut bytes { + *byte = rng.r#gen(); + } + format!("0x{}", hex::encode(bytes)) +} + +/// Generates a random Ethereum consensus signature for testing. +pub fn random_eth2_signature_bytes() -> phase0::BLSSignature { + let mut signature = [0u8; 96]; + rand::thread_rng().fill(&mut signature[..]); + signature +} + /// Generate random Ethereum address for testing. pub fn random_eth_address(rand: &mut impl Rng) -> [u8; 20] { let mut bytes = [0u8; 20]; @@ -74,6 +102,134 @@ pub fn random_eth_address(rand: &mut impl Rng) -> [u8; 20] { bytes } +/// Generates a random 32-byte root as a hex string for testing. +/// +/// Returns a 32-byte (64 hex characters) root encoded as a hex string with "0x" +/// prefix. +pub fn random_root() -> String { + let mut bytes = [0u8; 32]; + let mut rng = rand::thread_rng(); + for byte in &mut bytes { + *byte = rng.r#gen(); + } + format!("0x{}", hex::encode(bytes)) +} + +/// Generates a random Ethereum consensus root for testing. +pub fn random_root_bytes() -> phase0::Root { + let mut root = [0u8; 32]; + rand::thread_rng().fill(&mut root); + root +} + +/// Generates a random slot for testing. +pub fn random_slot() -> phase0::Slot { + rand::thread_rng().r#gen() +} + +/// Generates a random validator index for testing. +pub fn random_v_idx() -> phase0::ValidatorIndex { + rand::thread_rng().r#gen() +} + +/// Generates a random bitlist as a hex string for testing. +/// +/// # Arguments +/// +/// * `length` - The number of bits to set in the bitlist +/// +/// Returns a hex-encoded bitlist string with "0x" prefix. +pub fn random_bit_list(length: usize) -> String { + // Create a byte array large enough to hold the bits + // For simplicity, use 32 bytes (256 bits) + let mut bytes = [0u8; 32]; + let mut rng = rand::thread_rng(); + + // Set 'length' random bits + for _ in 0..length { + let bit_idx = rng.r#gen::() % 256; + let byte_idx = bit_idx / 8; + let bit_offset = bit_idx % 8; + bytes[byte_idx] |= 1 << bit_offset; + } + + format!("0x{}", hex::encode(bytes)) +} + +/// Generates a random checkpoint for testing. +fn random_checkpoint() -> AltairBeaconStateCurrentJustifiedCheckpoint { + let mut rng = rand::thread_rng(); + AltairBeaconStateCurrentJustifiedCheckpoint { + epoch: rng.r#gen::().to_string(), + root: random_root(), + } +} + +/// Generates random attestation data for Phase 0. +fn random_attestation_data_phase0() -> Data { + let mut rng = rand::thread_rng(); + Data { + slot: rng.r#gen::().to_string(), + index: rng.r#gen::().to_string(), + beacon_block_root: random_root(), + source: random_checkpoint(), + target: random_checkpoint(), + } +} + +/// Generates a random Phase 0 attestation. +/// +/// Returns an attestation with random aggregation bits, attestation data, and +/// signature. +pub fn random_phase0_attestation() -> GetBlockAttestationsV2ResponseResponseDataArray2 { + GetBlockAttestationsV2ResponseResponseDataArray2 { + aggregation_bits: random_bit_list(1), + data: random_attestation_data_phase0(), + signature: random_eth2_signature(), + } +} + +/// Generates a random Deneb versioned attestation. +/// +/// Returns a versioned attestation containing a Phase 0 attestation with the +/// Deneb version tag. This matches the Go implementation: +/// +/// ```go +/// func RandomDenebVersionedAttestation() *eth2spec.VersionedAttestation { +/// return ð2spec.VersionedAttestation{ +/// Version: eth2spec.DataVersionDeneb, +/// Deneb: RandomPhase0Attestation(), +/// } +/// } +/// ``` +pub fn random_deneb_versioned_attestation() -> versioned::VersionedAttestation { + let mut rng = rand::thread_rng(); + + let attestation = phase0::Attestation { + aggregation_bits: phase0::BitList::default(), + data: phase0::AttestationData { + slot: rng.r#gen(), + index: rng.r#gen(), + beacon_block_root: random_root_bytes(), + source: phase0::Checkpoint { + epoch: rng.r#gen(), + root: random_root_bytes(), + }, + target: phase0::Checkpoint { + epoch: rng.r#gen(), + root: random_root_bytes(), + }, + }, + signature: random_eth2_signature_bytes(), + }; + + versioned::VersionedAttestation { + version: versioned::DataVersion::Deneb, + validator_index: Some(rng.r#gen()), + attestation: Some(AttestationPayload::Deneb(attestation)), + } +} + #[cfg(test)] mod tests { use super::*; @@ -150,4 +306,92 @@ mod tests { "Different seeds should produce different BLS keys" ); } + + #[test] + fn test_random_eth2_signature() { + let sig1 = random_eth2_signature(); + let sig2 = random_eth2_signature(); + + // Check format + assert!(sig1.starts_with("0x")); + // 96 bytes = 192 hex chars + "0x" prefix = 194 total + assert_eq!(sig1.len(), 194); + + // Different calls should produce different signatures + assert_ne!(sig1, sig2); + } + + #[test] + fn test_random_root() { + let root1 = random_root(); + let root2 = random_root(); + + // Check format + assert!(root1.starts_with("0x")); + // 32 bytes = 64 hex chars + "0x" prefix = 66 total + assert_eq!(root1.len(), 66); + + // Different calls should produce different roots + assert_ne!(root1, root2); + } + + #[test] + fn test_random_bit_list() { + let bitlist = random_bit_list(5); + + // Check format + assert!(bitlist.starts_with("0x")); + // 32 bytes = 64 hex chars + "0x" prefix = 66 total + assert_eq!(bitlist.len(), 66); + } + + #[test] + fn test_random_phase0_attestation() { + let att = random_phase0_attestation(); + + // Check that all fields are populated + assert!(att.aggregation_bits.starts_with("0x")); + assert!(att.signature.starts_with("0x")); + assert!(att.data.beacon_block_root.starts_with("0x")); + assert!(!att.data.slot.is_empty()); + assert!(!att.data.index.is_empty()); + } + + #[test] + fn test_random_deneb_versioned_attestation() { + let versioned_att = random_deneb_versioned_attestation(); + + // Check version is Deneb + assert!(matches!( + versioned_att.version, + versioned::DataVersion::Deneb + )); + + // Check that data is populated + match versioned_att.attestation { + Some(AttestationPayload::Deneb(att)) => { + assert_eq!(att.signature.len(), 96); + } + _ => panic!("Expected Deneb attestation"), + } + } + + #[test] + fn test_random_deneb_versioned_attestation_different() { + let att1 = random_deneb_versioned_attestation(); + let att2 = random_deneb_versioned_attestation(); + + // Different calls should produce different attestations + // Check signatures are different + let sig1 = match &att1.attestation { + Some(AttestationPayload::Deneb(a)) => &a.signature, + _ => panic!("Expected Deneb attestation"), + }; + let sig2 = match &att2.attestation { + Some(AttestationPayload::Deneb(a)) => &a.signature, + _ => panic!("Expected Deneb attestation"), + }; + + assert_ne!(sig1, sig2); + } } diff --git a/deny.toml b/deny.toml index 20794394..2c06b6a5 100644 --- a/deny.toml +++ b/deny.toml @@ -59,7 +59,7 @@ allow = [ "Xnet", "Zlib", ] -exceptions = [{ crate = "attohttpc", allow = ["MPL-2.0"] }] +exceptions = [{ crate = "attohttpc", allow = ["MPL-2.0"] }, { crate = "dyn-eq", allow = ["MPL-2.0"] }] confidence-threshold = 0.8 unused-allowed-license = "allow"