diff --git a/Cargo.lock b/Cargo.lock index c8e4ae9..9a2e53b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "alloy" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1e915a830ea2ee123c9d3886bfec3302a7ddcd73a973ab165ed45aca2e42c3" +checksum = "d9677ab3454c237abe6307805bea17a7912b3a516b606c7b4d3d948b9fede137" dependencies = [ "alloy-consensus", "alloy-core", @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83447eeb17816e172f1dfc0db1f9dc0b7c5d069bd1f7cecbecceb382bf931015" +checksum = "a6ff0c4adba2abdcd9fb5829ae5f4394c06f8585ed283a9ba79aa33763c802e1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5406343e306856dc2be762700e98a16904de45dee14a07f233e742ce68daff2f" +checksum = "7cdf48932b1db3216175e19a2b476d89d53076e004850ee7983c6807ba0fde74" dependencies = [ "alloy-consensus", "alloy-eips", @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" +checksum = "b3b12337f74cbfa451cb04dac173974814a6ff463079e1793aa09600ba8813ab" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca4c89ace90684b4b77366d00631ed498c9af962079af2a5dbc593a0618a77" +checksum = "ea4c0453065b9206acc0f869a258dc8dcbbd595e144b4446f2c493a24a814d1f" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0a82e56b1843bce483942d54fcadea92e676f1bde162e93c7d3b621fabc4e1" +checksum = "c4691c60de5d628533752cd07e102d17c47874c06c04c91af33960fd94c484f4" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -181,9 +181,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db7b095b0b1db1d18ce7e91dcd2e82007f2d52bfb8125e6b64633a74a06bc3" +checksum = "9d912bb639bf4ac31e83095afe9e907c8b9774ce0c405966228368309fcfc45f" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd28d9bfd11729037d194f2b1d43db8642eb3f342032691f4ca96bb745479c3c" +checksum = "d875a11bd98595f57f73890f2e36f4a1cca0fa670623e59c9fb08b2389979c36" dependencies = [ "alloy-consensus", "alloy-eips", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -259,20 +259,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +checksum = "9d4311c03125e8a18296504560b9de3d75ecbd0dcda7f71e6cf2a196d57e6fba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "alloy-rpc-types" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052c031d1f7c5611997056bbcb8814e5cbf20f7efeee8c3de690555172038cf2" +checksum = "96deb317fe224e98a8ae17a7ed7ea63478867385bf50b7abd53642b24cfd811a" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6561ed4759c974d9c144500a59e3fb8c1d87327a12900d5ce455c0cae6dcb6" +checksum = "911a513723ef0b90c3b072f65eebf54d6ebc8b651d06734e252d024bd89b88ac" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", @@ -297,9 +297,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175a2a5b6017d7f61b5e4b800d21215fe8e94fe729d00828e13bb6d93dcf3492" +checksum = "4e1bd19904581ee2075b61faabab1a4cefa8b96d8f2c85343adeae8ecf615e2b" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc21a8772af7d78bba286726aa245bd2ff81cd9abe230afea2e91578996831c9" +checksum = "c1e97b3e0b9f816b25083045dcfa69431bd059a078e828e4d82d296d1949b96c" dependencies = [ "alloy-primitives", "serde", @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ffbce94c50dd9d4d1f83e044c5595bbd3ada981bd3057ce28b3a5470e77385d" +checksum = "6913d06ccc0d9c6ab67e2f82e2fbe292706da1f91442f30cded2d04b56bf0d58" dependencies = [ "alloy-primitives", "async-trait", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48366d2c42b8d95ef951101bafa28486590f21b7a1e68b6b2d069746557bbe3" +checksum = "c880813cd26bd1ddf8c2236e31295896efd1fcc5cc761d8894ae894503aeea53" dependencies = [ "alloy-consensus", "alloy-network", @@ -369,7 +369,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -386,7 +386,7 @@ dependencies = [ "proc-macro2", "quote", "sha3", - "syn 2.0.117", + "syn 2.0.118", "syn-solidity", ] @@ -402,7 +402,7 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "syn-solidity", ] @@ -446,14 +446,14 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a0035943b75fe1e249f52e688492d7a1b1826bc2d19b8e1d5d3c24a2ad8f50" +checksum = "406bc1183f6843e0aba09f7b3365e828b597213d60793ba5cb41befc863e3a78" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "ark-ff" @@ -529,6 +529,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -556,7 +573,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.118", ] [[package]] @@ -594,7 +621,20 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -630,6 +670,30 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -661,10 +725,20 @@ dependencies = [ ] [[package]] -name = "arrayvec" -version = "0.7.6" +name = "ark-std" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "async-trait" @@ -674,7 +748,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -685,7 +759,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -728,32 +802,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "bitcoin-io" -version = "0.1.100" +name = "bitcoin-consensus-encoding" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -772,9 +867,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -793,9 +888,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -804,15 +899,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -844,9 +939,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ "serde", ] @@ -868,9 +963,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ "find-msvc-tools", "shlex", @@ -1045,7 +1140,7 @@ dependencies = [ "quote", "serde", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1056,7 +1151,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1075,7 +1170,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -1109,7 +1203,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -1140,7 +1234,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "crypto-common 0.2.2", ] @@ -1180,7 +1274,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1214,22 +1308,22 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1400,15 +1494,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", ] [[package]] @@ -1456,7 +1548,7 @@ dependencies = [ [[package]] name = "hashgraph-like-consensus" -version = "0.5.1" +version = "0.6.0" dependencies = [ "alloy", "alloy-signer", @@ -1496,6 +1588,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1507,9 +1608,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1517,9 +1618,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -1548,12 +1649,6 @@ dependencies = [ "cc", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1577,7 +1672,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1638,13 +1733,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1674,9 +1768,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -1697,12 +1791,6 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" @@ -1732,9 +1820,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "macro-string" @@ -1744,14 +1832,14 @@ checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "multimap" @@ -1761,9 +1849,9 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -1849,7 +1937,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1883,9 +1971,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" dependencies = [ "memchr", "ucd-trie", @@ -1940,7 +2028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1982,7 +2070,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2015,9 +2103,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -2025,9 +2113,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck", "itertools 0.14.0", @@ -2038,28 +2126,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.117", + "syn 2.0.118", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] @@ -2072,9 +2160,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2170,9 +2258,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.1" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" dependencies = [ "rustversion", ] @@ -2203,14 +2291,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2231,9 +2319,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" @@ -2257,14 +2345,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +checksum = "45caf26f647c19115bf9c453c70ffe4a4a3a6390dceebd942610584f99b8ddce" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "ark-ff 0.5.0", + "ark-ff 0.6.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -2291,9 +2380,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc-hex" @@ -2487,7 +2576,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2532,7 +2621,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2568,9 +2657,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" dependencies = [ "cc", "cfg-if", @@ -2600,9 +2689,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] @@ -2648,9 +2737,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -2666,7 +2755,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2682,7 +2771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys", @@ -2705,7 +2794,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2719,12 +2808,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -2734,15 +2822,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -2812,7 +2900,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2874,11 +2962,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -2912,27 +3000,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2943,9 +3022,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2953,60 +3032,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver 1.0.28", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -3028,7 +3073,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3039,7 +3084,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3084,100 +3129,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver 1.0.28", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "wyz" version = "0.5.1" @@ -3189,42 +3146,42 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 439580e..0dd297b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hashgraph-like-consensus" -version = "0.5.1" +version = "0.6.0" edition = "2024" description = "A lightweight Rust library for making binary decisions in networks using hashgraph-style consensus" license = "MIT" diff --git a/README.md b/README.md index 9982434..ab34c81 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,17 @@ use hashgraph_like_consensus::{ types::CreateProposalRequest, }; use alloy::signers::local::PrivateKeySigner; +use std::time::{SystemTime, UNIX_EPOCH}; fn main() -> Result<(), Box> { let signer = EthereumConsensusSigner::new(PrivateKeySigner::random()); let service = DefaultConsensusService::new(signer.clone()); let scope = ScopeID::from("example-scope"); + // The caller supplies the current time (seconds since Unix epoch) to every + // time-sensitive call, so the application controls the time source. + let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + // Create a proposal let proposal = service.create_proposal( &scope, @@ -65,10 +70,11 @@ fn main() -> Result<(), Box> { 60, // expiration (seconds from now) true, // liveness: silent peers count as YES at timeout )?, + now, )?; // Cast a vote — the service uses its held signer. - let vote = service.cast_vote(&scope, proposal.proposal_id, true)?; + let vote = service.cast_vote(&scope, proposal.proposal_id, true, now)?; println!("Recorded vote {}", vote.vote_id); Ok(()) @@ -184,8 +190,9 @@ orchestration. Your application is responsible for: | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Network propagation** | The library performs no I/O. When you create a proposal or cast a vote, you must gossip it to peers yourself. When a message arrives from the network, call `process_incoming_proposal` or `process_incoming_vote`. | | **Timeout scheduling** | The library does not spawn timers. You must schedule a timer for each proposal (using `consensus_timeout()` from the config) and call `handle_consensus_timeout` when it fires. Without this, proposals with offline voters stay `Active` forever. | +| **Time source** | Every time-sensitive method takes `now` (seconds since Unix epoch) as a parameter. The application decides where time comes from — system time in production, a controllable clock in tests. | | **`expected_voters_count` accuracy** | This value drives all threshold math (`ceil(2n/3)` quorum, silent peer counting). If it doesn't match the actual group size, consensus results will be wrong. | -| **Signer management** | You construct each `ConsensusService` with the peer's `ConsensusSignatureScheme` value (e.g. `EthereumConsensusSigner::new(private_key)`). `cast_vote` uses that held signer. Each identity may vote at most once per proposal. | +| **Signer management** | You construct each `ConsensusService` with the peer's `ConsensusSignatureScheme` value (e.g. `EthereumConsensusSigner::new(private_key)`). `cast_vote` uses that held signer. Each identity may vote at most once per proposal. | | **Proposal ID tracking** | The library generates a `proposal_id` on creation. You must store it and pass it to every subsequent call (`cast_vote`, `handle_consensus_timeout`, etc.). | | **Session eviction awareness** | The default service keeps at most 10 sessions per scope (configurable via `new_with_max_sessions`). Older sessions are silently dropped when the limit is exceeded. Archive results before they are evicted. | @@ -259,6 +266,9 @@ service.scope(&scope)?.fast_consensus().initialize()?; ### Working with Proposals +Every time-sensitive call takes `now` — the current time in seconds since Unix +epoch, supplied by the application. + ```rust // Create a proposal let proposal = service.create_proposal(&scope, CreateProposalRequest::new( @@ -268,23 +278,23 @@ let proposal = service.create_proposal(&scope, CreateProposalRequest::new( 3, // expected voters 60, // expiration (seconds from now) true, // liveness: silent peers count as YES at timeout -)?)?; +)?, now)?; // Process a proposal received from the network -service.process_incoming_proposal(&scope, proposal)?; +service.process_incoming_proposal(&scope, proposal, now)?; ``` ### Casting and Processing Votes ```rust // Cast your vote (yes = true, no = false) using the service's held signer. -let vote = service.cast_vote(&scope, proposal_id, true)?; +let vote = service.cast_vote(&scope, proposal_id, true, now)?; // Cast a vote and get the updated proposal (useful for gossiping). -let proposal = service.cast_vote_and_get_proposal(&scope, proposal_id, true)?; +let proposal = service.cast_vote_and_get_proposal(&scope, proposal_id, true, now)?; // Process a vote received from the network (uses the service's scheme to verify). -service.process_incoming_vote(&scope, vote)?; +service.process_incoming_vote(&scope, vote, now)?; ``` ### Reading State (via Storage) @@ -332,7 +342,7 @@ after counting silent peers), which marks the session as failed. // Drive the timer however your app prefers, then call the handler when it fires. std::thread::sleep(config.consensus_timeout()); -match service.handle_consensus_timeout(&scope, proposal_id) { +match service.handle_consensus_timeout(&scope, proposal_id, now) { Ok(true) => println!("Consensus: YES"), Ok(false) => println!("Consensus: NO"), Err(ConsensusError::InsufficientVotesAtTimeout) => { @@ -469,13 +479,13 @@ See `tests/custom_scheme_tests.rs` for a working non-Ethereum example. The `utils` module provides low-level helpers for advanced use cases: -| Function | Description | -| ------------------------------------- | ------------------------------------------------------------------------ | -| `build_vote::()` | Create a signed vote linked into the hashgraph chain | -| `compute_vote_hash()` | Compute the deterministic hash of a vote | -| `validate_proposal::()` | Validate a proposal and all its votes against a signature scheme | -| `calculate_consensus_result()` | Determine result from collected votes using threshold and liveness rules | -| `has_sufficient_votes()` | Quick threshold check (count-based) | +| Function | Description | +| ------------------------------- | ------------------------------------------------------------------------ | +| `build_vote::()` | Create a signed vote linked into the hashgraph chain | +| `compute_vote_hash()` | Compute the deterministic hash of a vote | +| `validate_proposal::()` | Validate a proposal and all its votes against a signature scheme | +| `calculate_consensus_result()` | Determine result from collected votes using threshold and liveness rules | +| `has_sufficient_votes()` | Quick threshold check (count-based) | The generic `Signer` parameter on `build_vote` / `validate_proposal` / `validate_vote` selects which `ConsensusSignatureScheme` to use; pick it via diff --git a/src/error.rs b/src/error.rs index 3d0f2d9..61025c2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -71,6 +71,4 @@ pub enum ConsensusError { #[error("Signature scheme failure: {0}")] SignatureScheme(#[from] ConsensusSchemeError), - #[error("Failed to get current time")] - FailedToGetCurrentTime(#[from] std::time::SystemTimeError), } diff --git a/src/lib.rs b/src/lib.rs index 0701d77..3c82cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,9 @@ //! [`handle_consensus_timeout`](service::ConsensusService::handle_consensus_timeout) //! when it fires. Without this, proposals with offline voters stay `Active` //! forever and silent-peer liveness logic never runs. +//! - **Time source** — every time-sensitive method takes `now` (seconds since +//! Unix epoch) as a parameter, so the application controls where time comes +//! from (system time, a test clock, etc.). //! - **`expected_voters_count` accuracy** — this drives all threshold math; //! a wrong value produces wrong results. //! - **Session eviction awareness** — the default service keeps at most 10 @@ -59,11 +62,14 @@ //! types::CreateProposalRequest, //! }; //! use alloy::signers::local::PrivateKeySigner; +//! use std::time::{SystemTime, UNIX_EPOCH}; //! //! # fn example() -> Result<(), Box> { //! let signer = EthereumConsensusSigner::new(PrivateKeySigner::random()); //! let service = DefaultConsensusService::new(signer.clone()); //! let scope = ScopeID::from("my-scope"); +//! // The caller supplies the current time (seconds since Unix epoch). +//! let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); //! //! let proposal = service //! .create_proposal( @@ -76,9 +82,10 @@ //! 60, // expiration (seconds from now) //! true, // liveness: silent peers count as YES at timeout //! )?, +//! now, //! )?; //! -//! let vote = service.cast_vote(&scope, proposal.proposal_id, true)?; +//! let vote = service.cast_vote(&scope, proposal.proposal_id, true, now)?; //! # Ok(()) //! # } //! ``` @@ -117,3 +124,6 @@ pub mod signing; pub mod storage; pub mod types; pub mod utils; + +#[cfg(test)] +pub(crate) mod test_utils; diff --git a/src/scope.rs b/src/scope.rs index b843f02..0f02c7d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,10 +3,16 @@ use std::{fmt::Debug, hash::Hash}; /// A scope groups related proposals together. /// /// Think of it like a namespace or category. For example, you might use a scope as group of users. -/// Any type that can be used as a group of users can be used as a scope. +/// The trait is blanket-implemented: any `Clone + Eq + Hash + Send + Sync + Debug + 'static` +/// type works as a scope key — `String`, `Vec`, `[u8; 32]`, integers, or a +/// custom key type of your own. pub trait ConsensusScope: Clone + Eq + Hash + Send + Sync + Debug + 'static {} impl ConsensusScope for T where T: Clone + Eq + Hash + Send + Sync + Debug + 'static {} -/// A simple string-based scope identifier. +/// A simple string-based scope identifier, used by +/// [`DefaultConsensusService`](crate::service::DefaultConsensusService). +/// Integrations keyed by raw bytes can use `Vec` (or any other +/// [`ConsensusScope`] type) directly in their `ConsensusService` / +/// `ConsensusStorage` type parameters. pub type ScopeID = String; diff --git a/src/service.rs b/src/service.rs index 9eb543a..187a9f2 100644 --- a/src/service.rs +++ b/src/service.rs @@ -11,10 +11,7 @@ use crate::{ signing::ConsensusSignatureScheme, storage::ConsensusStorage, types::{ConsensusEvent, CreateProposalRequest, SessionTransition}, - utils::{ - build_vote, calculate_consensus_result, current_timestamp, validate_proposal_timestamp, - validate_vote, - }, + utils::{build_vote, calculate_consensus_result, validate_proposal_timestamp, validate_vote}, }; #[cfg(feature = "ethereum")] use crate::{ @@ -181,12 +178,15 @@ where /// /// Configuration is resolved from: proposal config > scope config > global default. /// If no config is provided, the scope's default configuration is used. + /// + /// `now` is the current time in seconds since Unix epoch, supplied by the caller. pub fn create_proposal( &self, scope: &Scope, request: CreateProposalRequest, + now: u64, ) -> Result { - self.create_proposal_with_config(scope, request, None) + self.create_proposal_with_config(scope, request, None, now) } /// Create a new proposal with an explicit [`ConsensusConfig`] override. @@ -197,11 +197,12 @@ where scope: &Scope, request: CreateProposalRequest, config: Option, + now: u64, ) -> Result { - let proposal = request.into_proposal()?; + let proposal = request.into_proposal(now)?; let config = self.resolve_config(scope, config, Some(&proposal))?; let (session, _) = - ConsensusSession::from_proposal::(proposal.clone(), config.clone())?; + ConsensusSession::from_proposal::(proposal.clone(), config.clone(), now)?; self.save_session(scope, session)?; self.trim_scope_sessions(scope)?; Ok(proposal) @@ -217,20 +218,21 @@ where scope: &Scope, proposal_id: u32, choice: bool, + now: u64, ) -> Result { let session = self.get_session(scope, proposal_id)?; - validate_proposal_timestamp(session.proposal.expiration_timestamp)?; + validate_proposal_timestamp(session.proposal.expiration_timestamp, now)?; if session.votes.contains_key(self.signer.identity()) { return Err(ConsensusError::UserAlreadyVoted); } - let vote = build_vote(&session.proposal, choice, &self.signer)?; + let vote = build_vote(&session.proposal, choice, &self.signer, now)?; let vote_clone = vote.clone(); let transition = self.update_session(scope, proposal_id, move |session| { - session.add_vote(vote_clone) + session.add_vote(vote_clone, now) })?; - self.handle_transition(scope, proposal_id, transition); + self.handle_transition(scope, proposal_id, transition, now); Ok(vote) } @@ -243,8 +245,9 @@ where scope: &Scope, proposal_id: u32, choice: bool, + now: u64, ) -> Result { - self.cast_vote(scope, proposal_id, choice)?; + self.cast_vote(scope, proposal_id, choice, now)?; let session = self.get_session(scope, proposal_id)?; Ok(session.proposal) } @@ -261,13 +264,15 @@ where &self, scope: &Scope, proposal: Proposal, + now: u64, ) -> Result<(), ConsensusError> { if self.get_session(scope, proposal.proposal_id).is_ok() { return Err(ConsensusError::ProposalAlreadyExist); } let config = self.resolve_config(scope, None, Some(&proposal))?; - let (session, transition) = ConsensusSession::from_proposal::(proposal, config)?; - self.handle_transition(scope, session.proposal.proposal_id, transition); + let (session, transition) = + ConsensusSession::from_proposal::(proposal, config, now)?; + self.handle_transition(scope, session.proposal.proposal_id, transition, now); self.save_session(scope, session)?; self.trim_scope_sessions(scope)?; Ok(()) @@ -278,17 +283,24 @@ where /// Call this when your networking layer delivers a vote from another peer. /// Validates the vote (signature, timestamp, chain) and adds it to the /// corresponding proposal session. May trigger consensus. - pub fn process_incoming_vote(&self, scope: &Scope, vote: Vote) -> Result<(), ConsensusError> { + pub fn process_incoming_vote( + &self, + scope: &Scope, + vote: Vote, + now: u64, + ) -> Result<(), ConsensusError> { let session = self.get_session(scope, vote.proposal_id)?; validate_vote::( &vote, session.proposal.expiration_timestamp, session.proposal.timestamp, + now, )?; let proposal_id = vote.proposal_id; - let transition = - self.update_session(scope, proposal_id, move |session| session.add_vote(vote))?; - self.handle_transition(scope, proposal_id, transition); + let transition = self.update_session(scope, proposal_id, move |session| { + session.add_vote(vote, now) + })?; + self.handle_transition(scope, proposal_id, transition, now); Ok(()) } @@ -312,6 +324,7 @@ where &self, scope: &Scope, proposal_id: u32, + now: u64, ) -> Result { let timeout_result: Result, ConsensusError> = self.update_session(scope, proposal_id, |session| { @@ -341,7 +354,7 @@ where ConsensusEvent::ConsensusReached { proposal_id, result: consensus_result, - timestamp: current_timestamp()?, + timestamp: now, }, ); Ok(consensus_result) @@ -351,7 +364,7 @@ where scope, ConsensusEvent::ConsensusFailed { proposal_id, - timestamp: current_timestamp()?, + timestamp: now, }, ); Err(ConsensusError::InsufficientVotesAtTimeout) @@ -517,14 +530,20 @@ where .ok_or(ConsensusError::ScopeNotFound) } - fn handle_transition(&self, scope: &Scope, proposal_id: u32, transition: SessionTransition) { + fn handle_transition( + &self, + scope: &Scope, + proposal_id: u32, + transition: SessionTransition, + now: u64, + ) { if let SessionTransition::ConsensusReached(result) = transition { self.emit_event( scope, ConsensusEvent::ConsensusReached { proposal_id, result, - timestamp: current_timestamp().unwrap_or(0), + timestamp: now, }, ); } diff --git a/src/session.rs b/src/session.rs index 03a0f47..0867f3c 100644 --- a/src/session.rs +++ b/src/session.rs @@ -13,7 +13,7 @@ use crate::{ signing::ConsensusSignatureScheme, types::SessionTransition, utils::{ - calculate_consensus_result, calculate_max_rounds, current_timestamp, validate_proposal, + calculate_consensus_result, calculate_max_rounds, validate_proposal, validate_proposal_timestamp, validate_vote, validate_vote_chain, }, }; @@ -180,8 +180,8 @@ pub struct ConsensusSession { impl ConsensusSession { /// Create a new session from a validated proposal (no votes). /// Used when creating proposals locally where we know the proposal is clean. - fn new(proposal: Proposal, config: ConsensusConfig) -> Self { - let now = current_timestamp().unwrap_or(0); + /// `now` (seconds since Unix epoch) becomes `created_at`. + fn new(proposal: Proposal, config: ConsensusConfig, now: u64) -> Self { Self { proposal, state: ConsensusState::Active, @@ -194,11 +194,13 @@ impl ConsensusSession { /// Create a session from a proposal, validating the proposal and all votes. /// This validates the proposal structure, vote chain, and individual votes before creating the session. /// The session is created with votes already processed and rounds correctly set. + /// `now` (seconds since Unix epoch) drives expiration checks and `created_at`. pub fn from_proposal( proposal: Proposal, config: ConsensusConfig, + now: u64, ) -> Result<(Self, SessionTransition), ConsensusError> { - validate_proposal::(&proposal)?; + validate_proposal::(&proposal, now)?; // Create clean proposal for session (votes will be added via initialize_with_votes) let existing_votes = proposal.votes.clone(); @@ -207,21 +209,27 @@ impl ConsensusSession { // Always start with round 1 for new proposals as we at least have the proposal owner's vote. clean_proposal.round = 1; - let mut session = Self::new(clean_proposal, config); + let mut session = Self::new(clean_proposal, config, now); let transition = session.initialize_with_votes::( existing_votes, proposal.expiration_timestamp, proposal.timestamp, + now, )?; Ok((session, transition)) } - /// Add a vote to the session. - pub(crate) fn add_vote(&mut self, vote: Vote) -> Result { + /// Add a vote to the session. Expiration is checked against `now` + /// (seconds since Unix epoch). + pub(crate) fn add_vote( + &mut self, + vote: Vote, + now: u64, + ) -> Result { match self.state { ConsensusState::Active => { - validate_proposal_timestamp(self.proposal.expiration_timestamp)?; + validate_proposal_timestamp(self.proposal.expiration_timestamp, now)?; // Check if adding this vote would exceed round limits self.check_round_limit(1)?; @@ -247,12 +255,13 @@ impl ConsensusSession { votes: Vec, expiration_timestamp: u64, creation_time: u64, + now: u64, ) -> Result { if !matches!(self.state, ConsensusState::Active) { return Err(ConsensusError::SessionNotActive); } - validate_proposal_timestamp(expiration_timestamp)?; + validate_proposal_timestamp(expiration_timestamp, now)?; if votes.is_empty() { return Ok(SessionTransition::StillActive); @@ -274,7 +283,7 @@ impl ConsensusSession { validate_vote_chain(&votes)?; for vote in &votes { - validate_vote::(vote, expiration_timestamp, creation_time)?; + validate_vote::(vote, expiration_timestamp, creation_time, now)?; } self.check_round_limit(votes.len())?; @@ -405,6 +414,7 @@ mod tests { error::ConsensusError, session::{ConsensusConfig, ConsensusSession, ConsensusState}, signing::EthereumConsensusSigner, + test_utils::now_ts, types::CreateProposalRequest, utils::build_vote, }; @@ -432,28 +442,28 @@ mod tests { ) .unwrap(); - let proposal = request.into_proposal().unwrap(); + let proposal = request.into_proposal(now_ts()).unwrap(); let config = ConsensusConfig::gossipsub(); - let mut session = ConsensusSession::new(proposal, config); + let mut session = ConsensusSession::new(proposal, config, now_ts()); // Round 1 -> Round 2 (first vote) - let vote1 = build_vote(&session.proposal, true, &wrap(signer1)).unwrap(); - session.add_vote(vote1).unwrap(); + let vote1 = build_vote(&session.proposal, true, &wrap(signer1), now_ts()).unwrap(); + session.add_vote(vote1, now_ts()).unwrap(); assert_eq!(session.proposal.round, 2); // Stay at round 2 (second vote) - let vote2 = build_vote(&session.proposal, false, &wrap(signer2)).unwrap(); - session.add_vote(vote2).unwrap(); + let vote2 = build_vote(&session.proposal, false, &wrap(signer2), now_ts()).unwrap(); + session.add_vote(vote2, now_ts()).unwrap(); assert_eq!(session.proposal.round, 2); // Stay at round 2 (third vote) - let vote3 = build_vote(&session.proposal, true, &wrap(signer3)).unwrap(); - session.add_vote(vote3).unwrap(); + let vote3 = build_vote(&session.proposal, true, &wrap(signer3), now_ts()).unwrap(); + session.add_vote(vote3, now_ts()).unwrap(); assert_eq!(session.proposal.round, 2); // Stay at round 2 (fourth vote) - should succeed - let vote4 = build_vote(&session.proposal, true, &wrap(signer4)).unwrap(); - session.add_vote(vote4).unwrap(); + let vote4 = build_vote(&session.proposal, true, &wrap(signer4), now_ts()).unwrap(); + session.add_vote(vote4, now_ts()).unwrap(); assert_eq!(session.proposal.round, 2); assert_eq!(session.votes.len(), 4); } @@ -479,37 +489,37 @@ mod tests { ) .unwrap(); - let proposal = request.into_proposal().unwrap(); + let proposal = request.into_proposal(now_ts()).unwrap(); let config = ConsensusConfig::p2p(); - let mut session = ConsensusSession::new(proposal, config); + let mut session = ConsensusSession::new(proposal, config, now_ts()); // Round 1 -> Round 2 (first vote, 1 vote total) - let vote1 = build_vote(&session.proposal, true, &wrap(signer1)).unwrap(); - session.add_vote(vote1).unwrap(); + let vote1 = build_vote(&session.proposal, true, &wrap(signer1), now_ts()).unwrap(); + session.add_vote(vote1, now_ts()).unwrap(); assert_eq!(session.proposal.round, 2); assert_eq!(session.votes.len(), 1); // Round 2 -> Round 3 (second vote, 2 votes total) - should succeed - let vote2 = build_vote(&session.proposal, false, &wrap(signer2)).unwrap(); - session.add_vote(vote2).unwrap(); + let vote2 = build_vote(&session.proposal, false, &wrap(signer2), now_ts()).unwrap(); + session.add_vote(vote2, now_ts()).unwrap(); assert_eq!(session.proposal.round, 3); assert_eq!(session.votes.len(), 2); // Round 3 -> Round 4 (third vote, 3 votes total) - should succeed - let vote3 = build_vote(&session.proposal, true, &wrap(signer3)).unwrap(); - session.add_vote(vote3).unwrap(); + let vote3 = build_vote(&session.proposal, true, &wrap(signer3), now_ts()).unwrap(); + session.add_vote(vote3, now_ts()).unwrap(); assert_eq!(session.proposal.round, 4); assert_eq!(session.votes.len(), 3); // Round 4 -> Round 5 (fourth vote, 4 votes total) - should succeed (dynamic limit = 4) - let vote4 = build_vote(&session.proposal, true, &wrap(signer4)).unwrap(); - session.add_vote(vote4).unwrap(); + let vote4 = build_vote(&session.proposal, true, &wrap(signer4), now_ts()).unwrap(); + session.add_vote(vote4, now_ts()).unwrap(); assert_eq!(session.proposal.round, 5); assert_eq!(session.votes.len(), 4); // Fifth vote would exceed dynamic max_round_limit (=4 votes) - let vote5 = build_vote(&session.proposal, true, &wrap(signer5)).unwrap(); - let err = session.add_vote(vote5).unwrap_err(); + let vote5 = build_vote(&session.proposal, true, &wrap(signer5), now_ts()).unwrap(); + let err = session.add_vote(vote5, now_ts()).unwrap_err(); assert!(matches!(err, ConsensusError::MaxRoundsExceeded)); } @@ -553,21 +563,28 @@ mod tests { true, ) .unwrap(); - let proposal = request.into_proposal().unwrap(); + let proposal = request.into_proposal(now_ts()).unwrap(); // Failed sessions reject new votes. let mut failed_session = - ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub()); + ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub(), now_ts()); failed_session.state = ConsensusState::Failed; - let vote = build_vote(&failed_session.proposal, true, &wrap(signer.clone())).unwrap(); - let err = failed_session.add_vote(vote).unwrap_err(); + let vote = build_vote( + &failed_session.proposal, + true, + &wrap(signer.clone()), + now_ts(), + ) + .unwrap(); + let err = failed_session.add_vote(vote, now_ts()).unwrap_err(); assert!(matches!(err, ConsensusError::SessionNotActive)); // Finalized sessions return existing transition/result. - let mut finalized_session = ConsensusSession::new(proposal, ConsensusConfig::gossipsub()); + let mut finalized_session = + ConsensusSession::new(proposal, ConsensusConfig::gossipsub(), now_ts()); finalized_session.state = ConsensusState::ConsensusReached(true); - let vote = build_vote(&finalized_session.proposal, true, &wrap(signer)).unwrap(); - let transition = finalized_session.add_vote(vote).unwrap(); + let vote = build_vote(&finalized_session.proposal, true, &wrap(signer), now_ts()).unwrap(); + let transition = finalized_session.add_vote(vote, now_ts()).unwrap(); assert!(matches!( transition, crate::types::SessionTransition::ConsensusReached(true) @@ -586,35 +603,41 @@ mod tests { true, ) .unwrap(); - let proposal = request.into_proposal().unwrap(); + let proposal = request.into_proposal(now_ts()).unwrap(); // Non-active sessions reject initialization. - let mut inactive = ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub()); + let mut inactive = + ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub(), now_ts()); inactive.state = ConsensusState::Failed; let err = inactive .initialize_with_votes::( vec![], proposal.expiration_timestamp, proposal.timestamp, + now_ts(), ) .unwrap_err(); assert!(matches!(err, ConsensusError::SessionNotActive)); // Duplicate owners are rejected before chain/signature checks. - let mut dup_session = ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub()); - let vote1 = build_vote(&dup_session.proposal, true, &wrap(signer.clone())).unwrap(); - let vote2 = build_vote(&dup_session.proposal, false, &wrap(signer)).unwrap(); + let mut dup_session = + ConsensusSession::new(proposal.clone(), ConsensusConfig::gossipsub(), now_ts()); + let vote1 = + build_vote(&dup_session.proposal, true, &wrap(signer.clone()), now_ts()).unwrap(); + let vote2 = build_vote(&dup_session.proposal, false, &wrap(signer), now_ts()).unwrap(); let err = dup_session .initialize_with_votes::( vec![vote1, vote2], proposal.expiration_timestamp, proposal.timestamp, + now_ts(), ) .unwrap_err(); assert!(matches!(err, ConsensusError::DuplicateVote)); // Explicitly exercise gossipsub projected round branch where vote_count == 0. - let mut zero_votes = ConsensusSession::new(proposal, ConsensusConfig::gossipsub()); + let mut zero_votes = + ConsensusSession::new(proposal, ConsensusConfig::gossipsub(), now_ts()); zero_votes.check_round_limit(0).unwrap(); } @@ -635,8 +658,8 @@ mod tests { ) .unwrap(); - let proposal = request.into_proposal().unwrap(); - let mut session = ConsensusSession::new(proposal, ConsensusConfig::p2p()); + let proposal = request.into_proposal(now_ts()).unwrap(); + let mut session = ConsensusSession::new(proposal, ConsensusConfig::p2p(), now_ts()); let wrapped_vote_count = (u32::MAX as usize) + 1; @@ -661,8 +684,8 @@ mod tests { ) .unwrap(); - let proposal = request.into_proposal().unwrap(); - let mut session = ConsensusSession::new(proposal, ConsensusConfig::p2p()); + let proposal = request.into_proposal(now_ts()).unwrap(); + let mut session = ConsensusSession::new(proposal, ConsensusConfig::p2p(), now_ts()); let starting_round = session.proposal.round; // vote_count at the u32 boundary should still advance the round via saturating_add diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..4c7f035 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,10 @@ +//! Helpers shared by the crate's inline test modules. + +/// Current time in seconds since Unix epoch — the caller-supplied `now` +/// the library expects. +pub(crate) fn now_ts() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() +} diff --git a/src/types.rs b/src/types.rs index 4d65e11..d0e00b7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,7 +8,7 @@ use std::time::Duration; use crate::{ error::ConsensusError, protos::consensus::v1::Proposal, - utils::{current_timestamp, generate_id, validate_expected_voters_count, validate_timeout}, + utils::{generate_id, validate_expected_voters_count, validate_timeout}, }; /// Events emitted by the consensus service when a proposal reaches a terminal state. @@ -84,11 +84,11 @@ impl CreateProposalRequest { /// Convert this request into an actual proposal. /// - /// Generates a unique proposal ID and sets the creation timestamp. The proposal - /// starts with round 1 and no votes. - pub fn into_proposal(self) -> Result { + /// Generates a unique proposal ID and stamps `now` (seconds since Unix epoch) + /// as the creation timestamp; the absolute expiration is derived from it. + /// The proposal starts with round 1 and no votes. + pub fn into_proposal(self, now: u64) -> Result { let proposal_id = generate_id(); - let now = current_timestamp()?; Ok(Proposal { name: self.name, @@ -108,6 +108,7 @@ impl CreateProposalRequest { #[cfg(test)] mod tests { use super::CreateProposalRequest; + use crate::test_utils::now_ts; #[test] fn into_proposal_should_not_overflow_expiration_timestamp() { @@ -124,7 +125,7 @@ mod tests { // Desired behavior: proposal creation should not panic on overflow-prone input, // and expiration should never be earlier than creation timestamp. let proposal = request - .into_proposal() + .into_proposal(now_ts()) .expect("proposal creation should handle large expiration safely"); assert!( diff --git a/src/utils.rs b/src/utils.rs index 3faa047..7090bb3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,10 +6,7 @@ use prost::Message; use sha2::{Digest, Sha256}; -use std::{ - collections::HashMap, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; +use std::{collections::HashMap, time::Duration}; use uuid::Uuid; use crate::{ @@ -54,13 +51,13 @@ pub fn compute_vote_hash(vote: &Vote) -> Vec { /// This builds a vote that links to previous votes in the hashgraph structure. /// The vote is signed with the provided signer and includes all the necessary /// fields for validation (parent_hash, received_hash, vote_hash, signature). +/// `now` (seconds since Unix epoch) becomes the vote's timestamp. pub fn build_vote( proposal: &Proposal, user_vote: bool, signer: &Signer, + now: u64, ) -> Result { - let now = current_timestamp()?; - let voter_identity = signer.identity(); // RFC Section 2.2: Define `parent_hash` as hash of previous owner's vote (empty if none). // RFC Section 2.3: Set `received_hash` to hash of immediately previous vote (last vote in list). @@ -102,20 +99,21 @@ pub fn build_vote( /// Validate a proposal and all its votes against a signature scheme. /// -/// Checks that the proposal hasn't expired. +/// Checks that the proposal hasn't expired as of `now` (seconds since Unix epoch). /// Also validates that all votes belong to this proposal, vote signatures are valid, /// and the vote chain (parent_hash/received_hash) is correct. /// Should be called when receiving a proposal from the network. pub fn validate_proposal( proposal: &Proposal, + now: u64, ) -> Result<(), ConsensusError> { - validate_proposal_timestamp(proposal.expiration_timestamp)?; + validate_proposal_timestamp(proposal.expiration_timestamp, now)?; for vote in proposal.votes.iter() { if vote.proposal_id != proposal.proposal_id { return Err(ConsensusError::VoteProposalIdMismatch); } - validate_vote::(vote, proposal.expiration_timestamp, proposal.timestamp)?; + validate_vote::(vote, proposal.expiration_timestamp, proposal.timestamp, now)?; } validate_vote_chain(&proposal.votes)?; Ok(()) @@ -130,6 +128,7 @@ pub(crate) fn validate_vote( vote: &Vote, expiration_timestamp: u64, creation_time: u64, + now: u64, ) -> Result<(), ConsensusError> { if vote.vote_owner.is_empty() { return Err(ConsensusError::EmptyVoteOwner); @@ -158,8 +157,6 @@ pub(crate) fn validate_vote( return Err(ConsensusError::InvalidVoteSignature); } - let now = current_timestamp()?; - // RFC Section 3.4: Check the `timestamp` against the replay attack. // In particular, the `timestamp` cannot be the old in the determined threshold. if vote.timestamp < creation_time { @@ -315,18 +312,15 @@ fn calculate_threshold_based_value(expected_voters: u32, consensus_threshold: f6 } } -pub(crate) fn current_timestamp() -> Result { - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); - Ok(now) -} - /// Check if a proposal has expired. /// /// RFC Section 2.5.4: Verifies that the proposal has not expired by checking that -/// the current time is less than the expiration timestamp. +/// `now` (seconds since Unix epoch) is less than the expiration timestamp. /// Returns an error if the proposal has expired. -pub(crate) fn validate_proposal_timestamp(expiration_timestamp: u64) -> Result<(), ConsensusError> { - let now = current_timestamp()?; +pub(crate) fn validate_proposal_timestamp( + expiration_timestamp: u64, + now: u64, +) -> Result<(), ConsensusError> { if now >= expiration_timestamp { return Err(ConsensusError::ProposalExpired); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..567b7ac --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,67 @@ +//! Helpers shared by the integration-test crates. +//! +//! Each test binary compiles this module independently and uses only a +//! subset of the helpers, so unused-code warnings are suppressed. +#![allow(dead_code)] + +use alloy::signers::local::PrivateKeySigner; +use hashgraph_like_consensus::{ + error::ConsensusError, + protos::consensus::v1::{Proposal, Vote}, + scope::ScopeID, + service::DefaultConsensusService, + signing::EthereumConsensusSigner, + storage::ConsensusStorage, + utils::build_vote, +}; + +/// Current time in seconds since Unix epoch — the caller-supplied `now` +/// the library expects. +pub fn now_ts() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() +} + +/// A service with in-memory storage and a fresh random signer. +pub fn make_service() -> DefaultConsensusService { + DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) +} + +/// Wrap a raw key into the Ethereum signature scheme. +pub fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { + EthereumConsensusSigner::new(signer) +} + +/// The signer's address bytes, as used for proposal/vote owner fields. +pub fn owner_bytes(signer: &PrivateKeySigner) -> Vec { + signer.address().as_slice().to_vec() +} + +/// Build and process a vote as if it arrived from a remote peer, returning +/// the vote for further gossip. +pub fn cast_remote_vote( + service: &DefaultConsensusService, + scope: &ScopeID, + proposal_id: u32, + choice: bool, + signer: &EthereumConsensusSigner, +) -> Result { + let proposal = service.storage().get_proposal(scope, proposal_id)?; + let vote = build_vote(&proposal, choice, signer, now_ts())?; + service.process_incoming_vote(scope, vote.clone(), now_ts())?; + Ok(vote) +} + +/// [`cast_remote_vote`], then return the updated proposal snapshot. +pub fn cast_remote_vote_and_get_proposal( + service: &DefaultConsensusService, + scope: &ScopeID, + proposal_id: u32, + choice: bool, + signer: &EthereumConsensusSigner, +) -> Result { + cast_remote_vote(service, scope, proposal_id, choice, signer)?; + service.storage().get_proposal(scope, proposal_id) +} diff --git a/tests/concurrency_tests.rs b/tests/concurrency_tests.rs index 165cb31..d10dc35 100644 --- a/tests/concurrency_tests.rs +++ b/tests/concurrency_tests.rs @@ -1,3 +1,6 @@ +mod common; +use common::{now_ts, wrap}; + use alloy::signers::local::PrivateKeySigner; use std::{ sync::{Arc, Barrier}, @@ -16,10 +19,6 @@ use hashgraph_like_consensus::{ types::CreateProposalRequest, }; -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - fn peer_service( storage: &InMemoryConsensusStorage, bus: &BroadcastEventBus, @@ -65,6 +64,7 @@ fn test_concurrent_vote_casting() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -81,7 +81,7 @@ fn test_concurrent_vote_casting() { let handle = thread::spawn(move || { barrier_clone.wait(); let peer = peer_service(&storage, &bus, wrap(PrivateKeySigner::random())); - peer.cast_vote(&scope_clone, proposal_id, i % 2 == 0) + peer.cast_vote(&scope_clone, proposal_id, i % 2 == 0, now_ts()) }); handles.push(handle); } @@ -124,6 +124,7 @@ fn test_concurrent_proposal_operations() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) }); handles.push(handle); @@ -165,6 +166,7 @@ fn test_concurrent_duplicate_vote_rejection() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -178,7 +180,8 @@ fn test_concurrent_duplicate_vote_rejection() { for _ in 0..EXPECTED_VOTERS_COUNT_5 { let voter = Arc::clone(&voter); let scope_clone = scope.clone(); - let handle = thread::spawn(move || voter.cast_vote(&scope_clone, proposal_id, true)); + let handle = + thread::spawn(move || voter.cast_vote(&scope_clone, proposal_id, true, now_ts())); handles.push(handle); } diff --git a/tests/consensus_service_tests.rs b/tests/consensus_service_tests.rs index 1992f8c..c2def83 100644 --- a/tests/consensus_service_tests.rs +++ b/tests/consensus_service_tests.rs @@ -1,3 +1,6 @@ +mod common; +use common::{cast_remote_vote, cast_remote_vote_and_get_proposal, make_service, now_ts, wrap}; + use alloy::signers::SignerSync; use alloy::signers::local::PrivateKeySigner; use hashgraph_like_consensus::signing::EthereumConsensusSigner; @@ -16,44 +19,6 @@ use hashgraph_like_consensus::{ utils::{build_vote, compute_vote_hash}, }; -fn cast_remote_vote( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Vote, - hashgraph_like_consensus::error::ConsensusError, -> { - let proposal = service.storage().get_proposal(scope, proposal_id)?; - let vote = build_vote(&proposal, choice, signer)?; - service.process_incoming_vote(scope, vote.clone())?; - Ok(vote) -} - -fn cast_remote_vote_and_get_proposal( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Proposal, - hashgraph_like_consensus::error::ConsensusError, -> { - cast_remote_vote(service, scope, proposal_id, choice, signer)?; - service.storage().get_proposal(scope, proposal_id) -} - -fn make_service() -> DefaultConsensusService { - DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) -} - -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - const SCOPE1_NAME: &str = "scope1"; const SCOPE2_NAME: &str = "scope2"; const PROPOSAL_NAME: &str = "Test Proposal"; @@ -92,6 +57,7 @@ fn setup_proposal( ) .expect("valid proposal request"), Some(consensus_config), + now_ts(), ) .expect("proposal should be created") } @@ -126,6 +92,7 @@ fn test_basic_consensus_flow() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -209,6 +176,7 @@ fn test_multi_scope_isolation() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("scope1 proposal"); @@ -234,6 +202,7 @@ fn test_multi_scope_isolation() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("scope2 proposal"); @@ -284,6 +253,7 @@ fn test_consensus_threshold_emits_event() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -370,7 +340,7 @@ fn test_handle_consensus_timeout_already_reached() { // Now call handle_consensus_timeout - should return the already reached consensus let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should return consensus result"); assert!(result, "should return true (YES consensus)"); @@ -415,7 +385,7 @@ fn test_handle_consensus_timeout_reaches_consensus() { // Call handle_consensus_timeout - should calculate consensus and reach it let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach consensus"); assert!(result, "should return true (YES consensus)"); @@ -488,7 +458,7 @@ fn test_handle_consensus_timeout_reaches_no_consensus_with_multiple_votes() { ); let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach consensus at timeout"); assert!(!result, "should return false (NO consensus)"); @@ -542,7 +512,7 @@ fn test_handle_consensus_timeout_resolves_with_liveness_yes() { // At timeout with liveness=true, silent peers count as YES → consensus reached let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach consensus with silent peers as YES"); assert!(result, "should return true (YES consensus)"); @@ -614,7 +584,7 @@ fn test_handle_consensus_timeout_insufficient_votes() { // Call handle_consensus_timeout - should fail (tied votes, no majority) let err = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect_err("should fail with insufficient votes"); assert!( @@ -677,7 +647,7 @@ fn test_handle_consensus_timeout_no_votes_liveness_true() { ); let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach consensus with silent peers as YES"); assert!(result, "should return true (all silent peers as YES)"); @@ -722,7 +692,7 @@ fn test_handle_consensus_timeout_no_votes_liveness_false() { ); let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach NO consensus with silent peers as NO"); assert!(!result, "should return false (all silent peers as NO)"); @@ -784,7 +754,7 @@ fn test_handle_consensus_timeout_reaches_consensus_p2p() { ); let result = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect("should reach consensus"); assert!(result, "should return true (YES consensus)"); @@ -846,7 +816,7 @@ fn test_handle_consensus_timeout_insufficient_votes_p2p() { ); let err = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect_err("should fail with insufficient votes"); assert!( @@ -894,15 +864,16 @@ fn test_cast_vote_rejects_same_voter_twice() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); service - .cast_vote(&scope, proposal.proposal_id, VOTE_YES) + .cast_vote(&scope, proposal.proposal_id, VOTE_YES, now_ts()) .expect("first vote should succeed"); let err = service - .cast_vote(&scope, proposal.proposal_id, VOTE_YES) + .cast_vote(&scope, proposal.proposal_id, VOTE_YES, now_ts()) .expect_err("second vote from same voter should fail"); assert!( @@ -930,11 +901,12 @@ fn test_process_incoming_proposal_rejects_duplicate_proposal() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); let err = service - .process_incoming_proposal(&scope, proposal) + .process_incoming_proposal(&scope, proposal, now_ts()) .expect_err("duplicate proposal should be rejected"); assert!( @@ -962,6 +934,7 @@ fn test_process_incoming_vote_rejects_unknown_session() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -988,7 +961,7 @@ fn test_process_incoming_vote_rejects_unknown_session() { .to_vec(); let err = service - .process_incoming_vote(&scope, invalid_vote) + .process_incoming_vote(&scope, invalid_vote, now_ts()) .expect_err("vote for unknown proposal should fail"); assert!( @@ -1003,7 +976,7 @@ fn test_handle_consensus_timeout_rejects_unknown_session() { let scope = ScopeID::from(SCOPE1_NAME); let err = service - .handle_consensus_timeout(&scope, u32::MAX) + .handle_consensus_timeout(&scope, u32::MAX, now_ts()) .expect_err("timeout handling for unknown proposal should fail"); assert!( @@ -1027,12 +1000,14 @@ fn test_process_incoming_proposal_rejects_expired_proposal() { true, ) .expect("valid proposal request"); - let proposal = request.into_proposal().expect("proposal should be created"); + let proposal = request + .into_proposal(now_ts()) + .expect("proposal should be created"); std::thread::sleep(Duration::from_secs(2)); let err = service - .process_incoming_proposal(&scope, proposal) + .process_incoming_proposal(&scope, proposal, now_ts()) .expect_err("expired incoming proposal should fail"); assert!( @@ -1060,6 +1035,7 @@ fn test_process_incoming_vote_rejects_invalid_vote_hash() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1073,12 +1049,12 @@ fn test_process_incoming_vote_rejects_invalid_vote_hash() { .expect("first vote should succeed"); let voter = PrivateKeySigner::random(); - let mut vote = - build_vote(&proposal, VOTE_YES, &wrap(voter)).expect("valid vote should be built"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter), now_ts()) + .expect("valid vote should be built"); vote.vote_hash = vec![1; 32]; let err = service - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect_err("tampered vote hash should fail"); assert!( @@ -1106,6 +1082,7 @@ fn test_process_incoming_vote_rejects_invalid_vote_signature() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1119,8 +1096,8 @@ fn test_process_incoming_vote_rejects_invalid_vote_signature() { .expect("first vote should succeed"); let voter = PrivateKeySigner::random(); - let mut vote = - build_vote(&proposal, VOTE_YES, &wrap(voter)).expect("valid vote should be built"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter), now_ts()) + .expect("valid vote should be built"); let wrong_signer = PrivateKeySigner::random(); let vote_bytes = vote.encode_to_vec(); let wrong_sig = wrong_signer @@ -1129,7 +1106,7 @@ fn test_process_incoming_vote_rejects_invalid_vote_signature() { vote.signature = wrong_sig.as_bytes().to_vec(); let err = service - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect_err("tampered signature should fail"); assert!( @@ -1157,6 +1134,7 @@ fn test_process_incoming_vote_rejects_duplicate_vote_owner() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1169,11 +1147,11 @@ fn test_process_incoming_vote_rejects_duplicate_vote_owner() { ) .expect("first owner vote should succeed"); - let duplicate_vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner)) + let duplicate_vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner), now_ts()) .expect("duplicate vote should be signable"); let err = service - .process_incoming_vote(&scope, duplicate_vote) + .process_incoming_vote(&scope, duplicate_vote, now_ts()) .expect_err("duplicate vote owner should fail"); assert!( @@ -1201,6 +1179,7 @@ fn test_process_incoming_vote_rejects_expired_vote_timestamp() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1214,8 +1193,8 @@ fn test_process_incoming_vote_rejects_expired_vote_timestamp() { .expect("first vote should succeed"); let voter = PrivateKeySigner::random(); - let mut vote = - build_vote(&proposal, VOTE_YES, &wrap(voter.clone())).expect("valid vote should be built"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter.clone()), now_ts()) + .expect("valid vote should be built"); vote.timestamp = proposal.expiration_timestamp.saturating_add(1); vote.vote_hash = compute_vote_hash(&vote); vote.signature.clear(); @@ -1227,7 +1206,7 @@ fn test_process_incoming_vote_rejects_expired_vote_timestamp() { .to_vec(); let err = service - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect_err("expired vote timestamp should fail"); assert!( @@ -1256,6 +1235,7 @@ fn test_handle_consensus_timeout_is_idempotent_for_failed_session() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1279,7 +1259,7 @@ fn test_handle_consensus_timeout_is_idempotent_for_failed_session() { .expect("second vote should succeed"); let err_first = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect_err("first timeout should fail consensus"); assert!(matches!( err_first, @@ -1287,7 +1267,7 @@ fn test_handle_consensus_timeout_is_idempotent_for_failed_session() { )); let err_second = service - .handle_consensus_timeout(&scope, proposal.proposal_id) + .handle_consensus_timeout(&scope, proposal.proposal_id, now_ts()) .expect_err("second timeout should keep failed consensus"); assert!(matches!( err_second, @@ -1306,7 +1286,7 @@ fn test_handle_consensus_timeout_rejects_unknown_scope() { let unknown_scope = ScopeID::from("unknown_scope"); let err = service - .handle_consensus_timeout(&unknown_scope, 1) + .handle_consensus_timeout(&unknown_scope, 1, now_ts()) .expect_err("unknown scope timeout handling should fail"); assert!( @@ -1364,7 +1344,7 @@ fn test_process_incoming_proposal_resolve_config_uses_base_timeout_when_expirati ) .expect("valid proposal request"); - let mut incoming = request.into_proposal().expect("proposal"); + let mut incoming = request.into_proposal(now_ts()).expect("proposal"); // Force expiration_timestamp <= timestamp while keeping both in the future, // so proposal remains non-expired but resolve_config must fall back to base timeout. @@ -1377,7 +1357,7 @@ fn test_process_incoming_proposal_resolve_config_uses_base_timeout_when_expirati incoming.expiration_timestamp = future; service - .process_incoming_proposal(&scope, incoming.clone()) + .process_incoming_proposal(&scope, incoming.clone(), now_ts()) .expect("incoming proposal should be accepted"); let resolved = service @@ -1416,6 +1396,7 @@ fn test_get_reached_proposals_with_consensus() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1468,6 +1449,7 @@ fn test_get_reached_proposals_no_consensus() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1504,6 +1486,7 @@ fn test_get_reached_proposals_mixed_states() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1530,6 +1513,7 @@ fn test_get_reached_proposals_mixed_states() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1556,6 +1540,7 @@ fn test_get_reached_proposals_mixed_states() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); diff --git a/tests/custom_scheme_tests.rs b/tests/custom_scheme_tests.rs index c421d2d..0ce69ce 100644 --- a/tests/custom_scheme_tests.rs +++ b/tests/custom_scheme_tests.rs @@ -7,6 +7,9 @@ //! signed by one peer and validated by another round-trip cleanly through the //! service without any Ethereum-specific assumptions. +mod common; +use common::now_ts; + use hashgraph_like_consensus::{ events::BroadcastEventBus, scope::ScopeID, @@ -107,17 +110,18 @@ fn stub_scheme_reaches_consensus_without_ethereum_types() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); owner - .cast_vote(&scope, proposal.proposal_id, true) + .cast_vote(&scope, proposal.proposal_id, true, now_ts()) .expect("owner vote"); voter_two - .cast_vote(&scope, proposal.proposal_id, true) + .cast_vote(&scope, proposal.proposal_id, true, now_ts()) .expect("voter two"); voter_three - .cast_vote(&scope, proposal.proposal_id, true) + .cast_vote(&scope, proposal.proposal_id, true, now_ts()) .expect("voter three"); let session = owner @@ -153,15 +157,16 @@ fn stub_scheme_rejects_forged_signature() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); - let mut vote = build_vote(&proposal, true, &voter).expect("vote"); + let mut vote = build_vote(&proposal, true, &voter, now_ts()).expect("vote"); // Tamper with the signature so verify() returns false. vote.signature.iter_mut().for_each(|b| *b ^= 0xFF); let err = owner - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect_err("forged signature must be rejected"); assert!( matches!( diff --git a/tests/network_gossip_tests.rs b/tests/network_gossip_tests.rs index ac6e311..5899a67 100644 --- a/tests/network_gossip_tests.rs +++ b/tests/network_gossip_tests.rs @@ -1,46 +1,20 @@ +mod common; +use common::{cast_remote_vote, make_service, now_ts, owner_bytes, wrap}; + use alloy::signers::local::PrivateKeySigner; use std::thread; use std::time::Duration; use hashgraph_like_consensus::{ - error::ConsensusError, scope::ScopeID, service::DefaultConsensusService, - session::ConsensusConfig, signing::EthereumConsensusSigner, storage::ConsensusStorage, - types::CreateProposalRequest, utils::build_vote, + error::ConsensusError, scope::ScopeID, session::ConsensusConfig, storage::ConsensusStorage, + types::CreateProposalRequest, }; -fn cast_remote_vote( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Vote, - hashgraph_like_consensus::error::ConsensusError, -> { - let proposal = service.storage().get_proposal(scope, proposal_id)?; - let vote = build_vote(&proposal, choice, signer)?; - service.process_incoming_vote(scope, vote.clone())?; - Ok(vote) -} - -fn make_service() -> DefaultConsensusService { - DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) -} - -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - const SCOPE: &str = "network_gossip_scope"; const PROPOSAL_NAME: &str = "Network Gossip Proposal"; const PROPOSAL_PAYLOAD: Vec = vec![]; const EXPIRATION: u64 = 120; -fn owner_bytes(signer: &PrivateKeySigner) -> Vec { - signer.address().as_slice().to_vec() -} - /// Peer A creates a proposal, gossips it to peer B, both vote YES, /// gossip votes back/forth, and both peers converge to Ok(true) consensus result. #[test] @@ -63,19 +37,20 @@ fn test_two_peers_gossip_reaches_unanimous_yes_for_n2() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("peer_a proposal"); // Gossip proposal to peer_b (decentralized: each peer stores locally). peer_b - .process_incoming_proposal(&scope, proposal.clone()) + .process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer_b accepts proposal"); // Peer A votes YES, gossip vote to peer B. let vote_a = cast_remote_vote(&peer_a, &scope, proposal.proposal_id, true, &wrap(owner_a)) .expect("peer_a vote"); peer_b - .process_incoming_vote(&scope, vote_a) + .process_incoming_vote(&scope, vote_a, now_ts()) .expect("peer_b accepts peer_a vote"); // Peer B votes YES, gossip vote to peer A. @@ -83,7 +58,7 @@ fn test_two_peers_gossip_reaches_unanimous_yes_for_n2() { let vote_b = cast_remote_vote(&peer_b, &scope, proposal.proposal_id, true, &wrap(owner_b)) .expect("peer_b vote"); peer_a - .process_incoming_vote(&scope, vote_b) + .process_incoming_vote(&scope, vote_b, now_ts()) .expect("peer_a accepts peer_b vote"); // Both peers should converge to the same consensus result. @@ -123,15 +98,16 @@ fn test_three_peers_gossip_converges_with_out_of_order_delivery() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("peer_a proposal"); // Gossip proposal to other peers peer_b - .process_incoming_proposal(&scope, proposal.clone()) + .process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer_b accepts proposal"); peer_c - .process_incoming_proposal(&scope, proposal.clone()) + .process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer_c accepts proposal"); // Two YES votes are sufficient for n=3 with threshold 2/3 and majority YES. @@ -143,18 +119,18 @@ fn test_three_peers_gossip_converges_with_out_of_order_delivery() { // Deliver to peer_c out-of-order (vote_b then vote_a). peer_c - .process_incoming_vote(&scope, vote_b.clone()) + .process_incoming_vote(&scope, vote_b.clone(), now_ts()) .expect("peer_c accepts vote_b"); peer_c - .process_incoming_vote(&scope, vote_a.clone()) + .process_incoming_vote(&scope, vote_a.clone(), now_ts()) .expect("peer_c accepts vote_a"); // Deliver to peer_a and peer_b (in-order doesn't matter either). peer_a - .process_incoming_vote(&scope, vote_b) + .process_incoming_vote(&scope, vote_b, now_ts()) .expect("peer_a accepts vote_b"); peer_b - .process_incoming_vote(&scope, vote_a) + .process_incoming_vote(&scope, vote_a, now_ts()) .expect("peer_b accepts vote_a"); let res_a = peer_a @@ -202,33 +178,34 @@ fn test_multi_peer_timeout_task_converges_to_failed() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("peer_a proposal"); peer_b - .process_incoming_proposal(&scope, proposal.clone()) + .process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer_b accepts proposal"); peer_c - .process_incoming_proposal(&scope, proposal.clone()) + .process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer_c accepts proposal"); // 2 YES votes total. let vote_a = cast_remote_vote(&peer_a, &scope, proposal.proposal_id, true, &wrap(owner_a)) .expect("peer_a vote"); peer_b - .process_incoming_vote(&scope, vote_a.clone()) + .process_incoming_vote(&scope, vote_a.clone(), now_ts()) .expect("peer_b accepts vote_a"); peer_c - .process_incoming_vote(&scope, vote_a) + .process_incoming_vote(&scope, vote_a, now_ts()) .expect("peer_c accepts vote_a"); let vote_b = cast_remote_vote(&peer_b, &scope, proposal.proposal_id, true, &wrap(voter_b)) .expect("peer_b vote"); peer_a - .process_incoming_vote(&scope, vote_b.clone()) + .process_incoming_vote(&scope, vote_b.clone(), now_ts()) .expect("peer_a accepts vote_b"); peer_c - .process_incoming_vote(&scope, vote_b) + .process_incoming_vote(&scope, vote_b, now_ts()) .expect("peer_c accepts vote_b"); // App-style scheduling: each peer runs its own timeout task. @@ -241,15 +218,15 @@ fn test_multi_peer_timeout_task_converges_to_failed() { let peer_c_task = peer_c.clone(); let ha = thread::spawn(move || { thread::sleep(Duration::from_millis(25)); - let _ = peer_a_task.handle_consensus_timeout(&scope_a, proposal_id); + let _ = peer_a_task.handle_consensus_timeout(&scope_a, proposal_id, now_ts()); }); let hb = thread::spawn(move || { thread::sleep(Duration::from_millis(25)); - let _ = peer_b_task.handle_consensus_timeout(&scope_b, proposal_id); + let _ = peer_b_task.handle_consensus_timeout(&scope_b, proposal_id, now_ts()); }); let hc = thread::spawn(move || { thread::sleep(Duration::from_millis(25)); - let _ = peer_c_task.handle_consensus_timeout(&scope_c, proposal_id); + let _ = peer_c_task.handle_consensus_timeout(&scope_c, proposal_id, now_ts()); }); ha.join().expect("timeout task A"); @@ -301,11 +278,12 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("peer_a proposal"); for peer in [&peer_b, &peer_c, &peer_d] { - peer.process_incoming_proposal(&scope, proposal.clone()) + peer.process_incoming_proposal(&scope, proposal.clone(), now_ts()) .expect("peer accepts proposal"); } @@ -314,7 +292,7 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { let vote_a = cast_remote_vote(&peer_a, &scope, proposal.proposal_id, true, &wrap(owner_a)) .expect("vote_a"); for peer in [&peer_b, &peer_c, &peer_d] { - peer.process_incoming_vote(&scope, vote_a.clone()) + peer.process_incoming_vote(&scope, vote_a.clone(), now_ts()) .expect("peer accepts vote_a"); } @@ -322,7 +300,7 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { let vote_b = cast_remote_vote(&peer_b, &scope, proposal.proposal_id, true, &wrap(voter_b)) .expect("vote_b"); for peer in [&peer_a, &peer_c, &peer_d] { - peer.process_incoming_vote(&scope, vote_b.clone()) + peer.process_incoming_vote(&scope, vote_b.clone(), now_ts()) .expect("peer accepts vote_b"); } @@ -330,7 +308,7 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { let vote_c = cast_remote_vote(&peer_c, &scope, proposal.proposal_id, false, &wrap(voter_c)) .expect("vote_c"); for peer in [&peer_a, &peer_b, &peer_d] { - peer.process_incoming_vote(&scope, vote_c.clone()) + peer.process_incoming_vote(&scope, vote_c.clone(), now_ts()) .expect("peer accepts vote_c"); } @@ -338,7 +316,7 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { let vote_d = cast_remote_vote(&peer_d, &scope, proposal.proposal_id, false, &wrap(voter_d)) .expect("vote_d"); for peer in [&peer_a, &peer_b, &peer_c] { - peer.process_incoming_vote(&scope, vote_d.clone()) + peer.process_incoming_vote(&scope, vote_d.clone(), now_ts()) .expect("peer accepts vote_d"); } @@ -355,19 +333,19 @@ fn test_multi_peer_timeout_task_resolves_tie_by_liveness_criteria_yes() { let ha = thread::spawn(move || { thread::sleep(Duration::from_millis(10)); - let _ = peer_a_task.handle_consensus_timeout(&scope_a, proposal_id); + let _ = peer_a_task.handle_consensus_timeout(&scope_a, proposal_id, now_ts()); }); let hb = thread::spawn(move || { thread::sleep(Duration::from_millis(10)); - let _ = peer_b_task.handle_consensus_timeout(&scope_b, proposal_id); + let _ = peer_b_task.handle_consensus_timeout(&scope_b, proposal_id, now_ts()); }); let hc = thread::spawn(move || { thread::sleep(Duration::from_millis(10)); - let _ = peer_c_task.handle_consensus_timeout(&scope_c, proposal_id); + let _ = peer_c_task.handle_consensus_timeout(&scope_c, proposal_id, now_ts()); }); let hd = thread::spawn(move || { thread::sleep(Duration::from_millis(10)); - let _ = peer_d_task.handle_consensus_timeout(&scope_d, proposal_id); + let _ = peer_d_task.handle_consensus_timeout(&scope_d, proposal_id, now_ts()); }); ha.join().expect("timeout task A"); diff --git a/tests/rfc_compliance_tests.rs b/tests/rfc_compliance_tests.rs index 21237d6..c7ebaca 100644 --- a/tests/rfc_compliance_tests.rs +++ b/tests/rfc_compliance_tests.rs @@ -1,56 +1,21 @@ +mod common; +use common::{ + cast_remote_vote, cast_remote_vote_and_get_proposal, make_service, now_ts, owner_bytes, wrap, +}; + use alloy::signers::{SignerSync, local::PrivateKeySigner}; -use hashgraph_like_consensus::signing::EthereumConsensusSigner; use prost::Message; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use hashgraph_like_consensus::{ error::ConsensusError, scope::ScopeID, - service::DefaultConsensusService, session::ConsensusConfig, storage::ConsensusStorage, types::CreateProposalRequest, utils::{build_vote, compute_vote_hash}, }; -fn cast_remote_vote( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Vote, - hashgraph_like_consensus::error::ConsensusError, -> { - let proposal = service.storage().get_proposal(scope, proposal_id)?; - let vote = build_vote(&proposal, choice, signer)?; - service.process_incoming_vote(scope, vote.clone())?; - Ok(vote) -} - -fn cast_remote_vote_and_get_proposal( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Proposal, - hashgraph_like_consensus::error::ConsensusError, -> { - cast_remote_vote(service, scope, proposal_id, choice, signer)?; - service.storage().get_proposal(scope, proposal_id) -} - -fn make_service() -> DefaultConsensusService { - DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) -} - -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - const SCOPE: &str = "rfc_compliance_scope"; const PROPOSAL_NAME: &str = "RFC Compliance Test"; const PROPOSAL_PAYLOAD: Vec = vec![]; @@ -69,10 +34,6 @@ const EXPECTED_VOTERS_COUNT_1: u32 = 1; const VOTE_YES: bool = true; const VOTE_NO: bool = false; -fn owner_bytes(signer: &PrivateKeySigner) -> Vec { - signer.address().as_slice().to_vec() -} - /// Test that proposal initialization has round = 1 #[test] fn test_proposal_initialization_round_is_one() { @@ -93,6 +54,7 @@ fn test_proposal_initialization_round_is_one() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -119,6 +81,7 @@ fn test_round_increments_on_vote_p2p() { ) .expect("valid proposal request"), Some(ConsensusConfig::p2p()), + now_ts(), ) .expect("proposal should be created"); @@ -179,6 +142,7 @@ fn test_gossipsub_rounds_stay_at_two() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -266,6 +230,7 @@ fn test_gossipsub_allows_multiple_votes_in_round_two() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -331,6 +296,7 @@ fn test_p2p_dynamic_max_rounds() { ) .expect("valid proposal request"), Some(ConsensusConfig::p2p()), + now_ts(), ) .expect("proposal should be created"); @@ -420,6 +386,7 @@ fn test_p2p_ceil_calculation_edge_cases() { ) .expect("valid proposal request"), Some(ConsensusConfig::p2p()), + now_ts(), ) .expect("proposal should be created"); @@ -469,26 +436,31 @@ fn test_gossipsub_batch_vote_processing() { ) .expect("valid proposal request"); - let mut proposal = request.into_proposal().expect("proposal should be created"); + let mut proposal = request + .into_proposal(now_ts()) + .expect("proposal should be created"); // Add votes to the proposal (simulating votes received from network) let voter1 = PrivateKeySigner::random(); - let vote1 = build_vote(&proposal, VOTE_YES, &wrap(voter1)).expect("vote should be created"); + let vote1 = + build_vote(&proposal, VOTE_YES, &wrap(voter1), now_ts()).expect("vote should be created"); proposal.votes.push(vote1); proposal.round = 2; // Gossipsub: round 2 after first vote let voter2 = PrivateKeySigner::random(); - let vote2 = build_vote(&proposal, VOTE_YES, &wrap(voter2)).expect("vote should be created"); + let vote2 = + build_vote(&proposal, VOTE_YES, &wrap(voter2), now_ts()).expect("vote should be created"); proposal.votes.push(vote2); // Round stays at 2 for gossipsub let voter3 = PrivateKeySigner::random(); - let vote3 = build_vote(&proposal, VOTE_YES, &wrap(voter3)).expect("vote should be created"); + let vote3 = + build_vote(&proposal, VOTE_YES, &wrap(voter3), now_ts()).expect("vote should be created"); proposal.votes.push(vote3); // Process the proposal with multiple votes (batch processing) // This should work in gossipsub mode - all votes are in round 2 - let result = service.process_incoming_proposal(&scope, proposal.clone()); + let result = service.process_incoming_proposal(&scope, proposal.clone(), now_ts()); assert!( result.is_ok(), "Gossipsub: Should accept batch votes in round 2" @@ -533,7 +505,9 @@ fn test_p2p_batch_vote_processing() { ) .expect("valid proposal request"); - let mut proposal = request.into_proposal().expect("proposal should be created"); + let mut proposal = request + .into_proposal(now_ts()) + .expect("proposal should be created"); // Add votes up to the limit (6 votes) let mut voters = vec![proposal_owner]; @@ -542,14 +516,14 @@ fn test_p2p_batch_vote_processing() { } for (i, voter) in voters.iter().enumerate() { - let vote = - build_vote(&proposal, VOTE_YES, &wrap(voter.clone())).expect("vote should be created"); + let vote = build_vote(&proposal, VOTE_YES, &wrap(voter.clone()), now_ts()) + .expect("vote should be created"); proposal.votes.push(vote); proposal.round = (i + 2) as u32; // P2P: round increments per vote } // Process the proposal with 6 votes (at the limit) - let result = service.process_incoming_proposal(&scope, proposal.clone()); + let result = service.process_incoming_proposal(&scope, proposal.clone(), now_ts()); assert!( result.is_ok(), "P2P: Should accept batch votes up to ceil(2n/3)" @@ -619,6 +593,7 @@ fn test_consensus_reachable_in_both_modes() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -661,6 +636,7 @@ fn test_consensus_reachable_in_both_modes() { ) .expect("valid proposal request"), Some(ConsensusConfig::p2p()), + now_ts(), ) .expect("proposal should be created"); @@ -708,6 +684,7 @@ fn test_n_le_2_requires_unanimous_yes() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -743,6 +720,7 @@ fn test_n_le_2_requires_unanimous_yes() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -788,6 +766,7 @@ fn test_n_le_2_requires_unanimous_yes() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -844,6 +823,7 @@ fn test_n_gt_2_consensus_requirements() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -913,6 +893,7 @@ fn test_expired_proposal_rejected() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -960,6 +941,7 @@ fn test_timestamp_replay_attack_protection() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -980,7 +962,8 @@ fn test_timestamp_replay_attack_protection() { .saturating_sub(EXPIRATION * 2); // More than expiration time let voter = PrivateKeySigner::random(); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter.clone())).expect("create vote"); + let mut vote = + build_vote(&proposal, VOTE_YES, &wrap(voter.clone()), now_ts()).expect("create vote"); // Manually set old timestamp vote.timestamp = old_timestamp; @@ -994,7 +977,7 @@ fn test_timestamp_replay_attack_protection() { .to_vec(); let err = service - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect_err("Should reject vote with old timestamp"); assert!( @@ -1026,6 +1009,7 @@ fn test_equality_of_votes_handling() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); @@ -1100,6 +1084,7 @@ fn test_equality_of_votes_handling() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal should be created"); diff --git a/tests/scope_config_tests.rs b/tests/scope_config_tests.rs index 875a46e..c14f080 100644 --- a/tests/scope_config_tests.rs +++ b/tests/scope_config_tests.rs @@ -1,16 +1,13 @@ +mod common; +use common::{make_service, now_ts}; + use std::time::Duration; -use alloy::signers::local::PrivateKeySigner; use hashgraph_like_consensus::{ - error::ConsensusError, scope::ScopeID, scope_config::NetworkType, - service::DefaultConsensusService, session::ConsensusConfig, signing::EthereumConsensusSigner, + error::ConsensusError, scope::ScopeID, scope_config::NetworkType, session::ConsensusConfig, storage::ConsensusStorage, types::CreateProposalRequest, }; -fn make_service() -> DefaultConsensusService { - DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) -} - const SCOPE_NAME: &str = "test_scope"; const PROPOSAL_PAYLOAD: Vec = vec![]; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); @@ -257,7 +254,7 @@ fn create_proposal_with_config_preserves_override_timeout() { .unwrap(); let proposal = service - .create_proposal_with_config(&scope, request, Some(override_config)) + .create_proposal_with_config(&scope, request, Some(override_config), now_ts()) .unwrap(); let config = service diff --git a/tests/storage_stream_tests.rs b/tests/storage_stream_tests.rs index 9fb840a..c43810c 100644 --- a/tests/storage_stream_tests.rs +++ b/tests/storage_stream_tests.rs @@ -1,3 +1,6 @@ +mod common; +use common::now_ts; + use hashgraph_like_consensus::signing::EthereumConsensusSigner; use hashgraph_like_consensus::{ @@ -23,12 +26,13 @@ fn make_session(name: &str) -> ConsensusSession { true, ) .expect("valid proposal request") - .into_proposal() + .into_proposal(now_ts()) .expect("proposal"); let (session, _) = ConsensusSession::from_proposal::( proposal, ConsensusConfig::gossipsub(), + now_ts(), ) .expect("session"); session diff --git a/tests/vote_tests.rs b/tests/vote_tests.rs index ab98b3d..cc2284c 100644 --- a/tests/vote_tests.rs +++ b/tests/vote_tests.rs @@ -1,3 +1,6 @@ +mod common; +use common::{now_ts, wrap}; + use alloy::signers::local::PrivateKeySigner; use hashgraph_like_consensus::{ @@ -9,10 +12,6 @@ use hashgraph_like_consensus::{ utils::{build_vote, validate_proposal}, }; -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - const SCOPE: &str = "vote_scope"; const PROPOSAL_NAME: &str = "Vote Test Proposal"; const PROPOSAL_PAYLOAD: Vec = vec![]; @@ -42,15 +41,16 @@ fn test_received_hash_for_new_voter() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); let proposal = service - .cast_vote_and_get_proposal(&scope, proposal.proposal_id, VOTE_YES) + .cast_vote_and_get_proposal(&scope, proposal.proposal_id, VOTE_YES, now_ts()) .expect("proposal_owner vote"); let other_voter = wrap(PrivateKeySigner::random()); - let vote = build_vote(&proposal, VOTE_YES, &other_voter).expect("second vote"); + let vote = build_vote(&proposal, VOTE_YES, &other_voter, now_ts()).expect("second vote"); assert!( vote.parent_hash.is_empty(), @@ -63,7 +63,7 @@ fn test_received_hash_for_new_voter() { let mut proposal_with_vote = proposal.clone(); proposal_with_vote.votes.push(vote); - validate_proposal::(&proposal_with_vote) + validate_proposal::(&proposal_with_vote, now_ts()) .expect("proposal with second voter should validate"); } @@ -86,15 +86,17 @@ fn test_parent_hash_for_same_voter() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); let proposal = service - .cast_vote_and_get_proposal(&scope, proposal.proposal_id, VOTE_YES) + .cast_vote_and_get_proposal(&scope, proposal.proposal_id, VOTE_YES, now_ts()) .expect("proposal_owner vote"); // Create a second vote from the same voter to exercise parent_hash logic. - let second_vote = build_vote(&proposal, VOTE_NO, &proposal_owner).expect("second vote"); + let second_vote = + build_vote(&proposal, VOTE_NO, &proposal_owner, now_ts()).expect("second vote"); assert!( second_vote.received_hash == proposal.votes[0].vote_hash, @@ -107,6 +109,6 @@ fn test_parent_hash_for_same_voter() { let mut proposal_with_vote = proposal.clone(); proposal_with_vote.votes.push(second_vote); - validate_proposal::(&proposal_with_vote) + validate_proposal::(&proposal_with_vote, now_ts()) .expect("proposal with parent hash chain should validate"); } diff --git a/tests/vote_validation_tests.rs b/tests/vote_validation_tests.rs index ce2455f..feae439 100644 --- a/tests/vote_validation_tests.rs +++ b/tests/vote_validation_tests.rs @@ -1,3 +1,6 @@ +mod common; +use common::{cast_remote_vote_and_get_proposal, make_service, now_ts, owner_bytes, wrap}; + use alloy::signers::{SignerSync, local::PrivateKeySigner}; use hashgraph_like_consensus::signing::EthereumConsensusSigner; @@ -6,51 +9,11 @@ use prost::Message; use hashgraph_like_consensus::{ error::ConsensusError, scope::ScopeID, - service::DefaultConsensusService, session::ConsensusConfig, - storage::ConsensusStorage, types::CreateProposalRequest, utils::{build_vote, compute_vote_hash, validate_proposal}, }; -fn cast_remote_vote( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Vote, - hashgraph_like_consensus::error::ConsensusError, -> { - let proposal = service.storage().get_proposal(scope, proposal_id)?; - let vote = build_vote(&proposal, choice, signer)?; - service.process_incoming_vote(scope, vote.clone())?; - Ok(vote) -} - -fn cast_remote_vote_and_get_proposal( - service: &DefaultConsensusService, - scope: &ScopeID, - proposal_id: u32, - choice: bool, - signer: &EthereumConsensusSigner, -) -> Result< - hashgraph_like_consensus::protos::consensus::v1::Proposal, - hashgraph_like_consensus::error::ConsensusError, -> { - cast_remote_vote(service, scope, proposal_id, choice, signer)?; - service.storage().get_proposal(scope, proposal_id) -} - -fn make_service() -> DefaultConsensusService { - DefaultConsensusService::new(EthereumConsensusSigner::new(PrivateKeySigner::random())) -} - -fn wrap(signer: PrivateKeySigner) -> EthereumConsensusSigner { - EthereumConsensusSigner::new(signer) -} - const SCOPE: &str = "validation_scope"; const PROPOSAL_NAME: &str = "Proposal"; const PROPOSAL_PAYLOAD: Vec = vec![]; @@ -63,10 +26,6 @@ const EXPECTED_VOTERS_COUNT_2: u32 = 2; const VOTE_YES: bool = true; const VOTE_NO: bool = false; -fn owner_bytes(signer: &PrivateKeySigner) -> Vec { - signer.address().as_slice().to_vec() -} - fn resign_vote( vote: &mut hashgraph_like_consensus::protos::consensus::v1::Vote, signer: &PrivateKeySigner, @@ -100,6 +59,7 @@ fn test_vote_created_with_helper_is_valid() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); @@ -113,10 +73,11 @@ fn test_vote_created_with_helper_is_valid() { .expect("proposal_owner vote"); let voter = PrivateKeySigner::random(); - let vote = build_vote(&proposal, VOTE_YES, &wrap(voter)).expect("vote should be created"); + let vote = + build_vote(&proposal, VOTE_YES, &wrap(voter), now_ts()).expect("vote should be created"); service - .process_incoming_vote(&scope, vote) + .process_incoming_vote(&scope, vote, now_ts()) .expect("vote should validate"); } @@ -139,6 +100,7 @@ fn test_invalid_signature_is_rejected() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); @@ -152,7 +114,7 @@ fn test_invalid_signature_is_rejected() { .expect("proposal_owner vote"); let voter = PrivateKeySigner::random(); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter)).expect("vote"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(voter), now_ts()).expect("vote"); let wrong_signer = PrivateKeySigner::random(); let vote_bytes = vote.encode_to_vec(); @@ -164,7 +126,7 @@ fn test_invalid_signature_is_rejected() { let mut invalid_proposal = proposal.clone(); invalid_proposal.votes.push(vote); - let err = validate_proposal::(&invalid_proposal) + let err = validate_proposal::(&invalid_proposal, now_ts()) .expect_err("validation should fail"); assert!( matches!(err, ConsensusError::InvalidVoteSignature), @@ -191,6 +153,7 @@ fn test_vote_chain_validation_rejects_bad_received_hash() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); @@ -206,8 +169,9 @@ fn test_vote_chain_validation_rejects_bad_received_hash() { let voter_one = PrivateKeySigner::random(); let voter_two = PrivateKeySigner::random(); - let vote_one = build_vote(&proposal, VOTE_YES, &wrap(voter_one)).expect("vote one"); - let mut vote_two = build_vote(&proposal, VOTE_NO, &wrap(voter_two.clone())).expect("vote two"); + let vote_one = build_vote(&proposal, VOTE_YES, &wrap(voter_one), now_ts()).expect("vote one"); + let mut vote_two = + build_vote(&proposal, VOTE_NO, &wrap(voter_two.clone()), now_ts()).expect("vote two"); vote_two.received_hash = vec![0; 32]; vote_two.vote_hash = compute_vote_hash(&vote_two); @@ -223,7 +187,7 @@ fn test_vote_chain_validation_rejects_bad_received_hash() { invalid.votes.push(vote_one); invalid.votes.push(vote_two); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("should fail chain validation"); assert!( matches!(err, ConsensusError::ReceivedHashMismatch), @@ -250,16 +214,17 @@ fn test_validate_proposal_rejects_empty_vote_owner() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner)).expect("vote"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner), now_ts()).expect("vote"); vote.vote_owner.clear(); let mut invalid = proposal; invalid.votes.push(vote); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("empty vote owner should fail"); assert!(matches!(err, ConsensusError::EmptyVoteOwner)); } @@ -283,16 +248,17 @@ fn test_validate_proposal_rejects_empty_vote_hash() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner)).expect("vote"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner), now_ts()).expect("vote"); vote.vote_hash.clear(); let mut invalid = proposal; invalid.votes.push(vote); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("empty vote hash should fail"); assert!(matches!(err, ConsensusError::EmptyVoteHash)); } @@ -316,16 +282,17 @@ fn test_validate_proposal_rejects_empty_signature() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner)).expect("vote"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner), now_ts()).expect("vote"); vote.signature.clear(); let mut invalid = proposal; invalid.votes.push(vote); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("empty signature should fail"); assert!(matches!(err, ConsensusError::EmptySignature)); } @@ -349,16 +316,17 @@ fn test_validate_proposal_rejects_mismatched_signature_length() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); - let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner)).expect("vote"); + let mut vote = build_vote(&proposal, VOTE_YES, &wrap(proposal_owner), now_ts()).expect("vote"); vote.signature = vec![7; 64]; let mut invalid = proposal; invalid.votes.push(vote); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("invalid signature length should fail"); // Signature-length checks now live in the scheme and surface as a // SignatureScheme error rather than a protocol-level MismatchedLength variant. @@ -384,14 +352,16 @@ fn test_vote_chain_validation_rejects_bad_parent_hash_owner_mismatch() { ) .expect("valid proposal request"), Some(ConsensusConfig::gossipsub()), + now_ts(), ) .expect("proposal"); let voter_one = PrivateKeySigner::random(); let voter_two = PrivateKeySigner::random(); - let vote_one = build_vote(&proposal, VOTE_YES, &wrap(voter_one)).expect("vote one"); - let mut vote_two = build_vote(&proposal, VOTE_NO, &wrap(voter_two.clone())).expect("vote two"); + let vote_one = build_vote(&proposal, VOTE_YES, &wrap(voter_one), now_ts()).expect("vote one"); + let mut vote_two = + build_vote(&proposal, VOTE_NO, &wrap(voter_two.clone()), now_ts()).expect("vote two"); // parent_hash points to another owner's vote, which should fail RFC parent-chain checks. vote_two.parent_hash = vote_one.vote_hash.clone(); @@ -401,7 +371,7 @@ fn test_vote_chain_validation_rejects_bad_parent_hash_owner_mismatch() { invalid.votes.push(vote_one); invalid.votes.push(vote_two); - let err = validate_proposal::(&invalid) + let err = validate_proposal::(&invalid, now_ts()) .expect_err("parent hash owner mismatch should fail"); assert!(matches!(err, ConsensusError::ParentHashMismatch)); }