diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index a675cc161..fc26b9b8f 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -2,14 +2,26 @@ ## Unreleased +### `malachitebft-core-consensus` +- Added new variants to `Input` enum: `PolkaCertificate` and `RoundCertificate` +- Added new variant to `Effect` enum: `PublishLivenessMessage` + ### `malachitebft-engine` - Changed the reply channel of `GetValidatorSet` message to take an `Option` instead of `Ctx::ValidatorSet`. +- Added new variant to `Msg` enum: `PublishLivenessMsg` +- Added new variants to `NetworkEvent` enum: `PolkaCertificate` and `RoundCertificate` - Changed `PartStore::all_parts` to `PartStore::all_parts_by_stream_id`: - Renamed method to clarify that, when a new part is received, the contiguous parts should be queried by stream id - Added required `StreamId` parameter - Added new public API `PartStore::all_parts_by_value_id` to be used instead of `PartStore::all_parts` when a decision is reached - Added `&StreamId` parameter to `part_store::PartStore::store` - Added `&StreamId` parameter to `part_store::PartStore::store_value_id` +- Changed semantics of `RestreamProposal` variant of `HostMsg`: the value at `round` should be now be restreamed if `valid_round` is `Nil` + +### `malachitebft-network` +- Added new variant to `Channel` enum: `Liveness` +- Renamed `Event::Message` variant to `Event::ConsensusMessage` +- Added new variant to `Event::LivenessMessage` ## 0.2.0 diff --git a/code/Cargo.lock b/code/Cargo.lock index c82430b71..e68998cdd 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -4,19 +4,13 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -70,14 +64,14 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -168,9 +162,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arrayref" @@ -374,17 +368,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -419,9 +413,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bitflags" @@ -517,9 +511,9 @@ dependencies = [ [[package]] name = "bytesize" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" dependencies = [ "serde", ] @@ -572,9 +566,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -617,9 +611,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -708,9 +702,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "color-eyre" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +checksum = "e6e1761c0e16f8883bbbb8ce5990867f4f06bf11a0253da6495a04ce4b6ef0ec" dependencies = [ "backtrace", "color-spantrace", @@ -723,9 +717,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +checksum = "2ddd8d5bfda1e11a501d0a7303f3bfed9aa632ebdb859be40d0fd70478ed70d5" dependencies = [ "once_cell", "owo-colors", @@ -959,9 +953,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -969,9 +963,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -983,9 +977,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -1008,15 +1002,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-encoding-macro" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1024,9 +1018,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", "syn", @@ -1034,9 +1028,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -1059,9 +1053,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -1192,7 +1186,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -1243,9 +1237,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1264,9 +1258,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -1312,12 +1306,12 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "miniz_oxide 0.8.5", + "miniz_oxide", ] [[package]] @@ -1328,9 +1322,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1509,9 +1503,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -1522,14 +1516,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1544,9 +1540,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1567,9 +1563,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -1577,7 +1573,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.8.0", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -1586,9 +1582,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -1611,9 +1607,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1649,9 +1645,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hermit-abi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -1685,7 +1681,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.0", + "rand 0.9.1", "socket2", "thiserror 2.0.12", "tinyvec", @@ -1707,7 +1703,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.0", + "rand 0.9.1", "resolv-conf", "smallvec", "thiserror 2.0.12", @@ -1733,17 +1729,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "http" version = "0.2.12" @@ -1840,9 +1825,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -1850,6 +1835,7 @@ dependencies = [ "http 1.3.1", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -1859,16 +1845,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -1882,21 +1869,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1905,31 +1893,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1937,67 +1905,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2017,9 +1972,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2098,12 +2053,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -2709,7 +2664,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi 0.5.1", "libc", "windows-sys 0.59.0", ] @@ -2784,7 +2739,7 @@ checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", ] @@ -2806,9 +2761,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libp2p" @@ -2820,7 +2775,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.15", + "getrandom 0.2.16", "libp2p-allow-block-list", "libp2p-connection-limits", "libp2p-core", @@ -2924,7 +2879,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "getrandom 0.2.15", + "getrandom 0.2.16", "hashlink", "hex_fmt", "libp2p-core", @@ -2936,7 +2891,7 @@ dependencies = [ "rand 0.8.5", "regex", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "tracing", "web-time", ] @@ -2977,7 +2932,7 @@ dependencies = [ "rand 0.8.5", "sec1", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 2.0.12", "tracing", "zeroize", @@ -3003,7 +2958,7 @@ dependencies = [ "quick-protobuf-codec", "rand 0.8.5", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "thiserror 2.0.12", "tracing", @@ -3263,15 +3218,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" @@ -3285,9 +3240,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loom" @@ -3308,7 +3263,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -3320,12 +3275,6 @@ dependencies = [ "twox-hash", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -3361,18 +3310,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -3637,9 +3577,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -3655,9 +3595,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -3685,9 +3625,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "3.5.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" [[package]] name = "p256" @@ -3698,7 +3638,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -3774,7 +3714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.8.0", + "indexmap 2.9.0", ] [[package]] @@ -3891,6 +3831,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3903,7 +3852,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.23", + "zerocopy", ] [[package]] @@ -3918,9 +3867,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.30" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", @@ -3937,9 +3886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -4019,12 +3968,6 @@ dependencies = [ "prost", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-protobuf" version = "0.8.1" @@ -4049,11 +3992,12 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases", "futures-io", "pin-project-lite", "quinn-proto", @@ -4064,17 +4008,18 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.2", + "rand 0.9.1", "ring", "rustc-hash", "rustls", @@ -4088,9 +4033,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", @@ -4109,6 +4054,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "ractor" version = "0.14.7" @@ -4138,13 +4089,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", ] [[package]] @@ -4173,7 +4123,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4182,7 +4132,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -4229,9 +4179,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -4242,7 +4192,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -4293,13 +4243,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resolv-conf" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] +checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" [[package]] name = "rfc6979" @@ -4319,7 +4265,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -4388,38 +4334,39 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.2", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] @@ -4434,9 +4381,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "ring", "rustls-pki-types", @@ -4615,7 +4562,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.8.0", + "indexmap 2.9.0", "serde", "serde_derive", "serde_json", @@ -4662,9 +4609,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -4698,9 +4645,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -4715,6 +4662,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "size-of" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e36eca171fddeda53901b0a436573b3f2391eaa9189d439b2bd8ea8cebd7e3" + [[package]] name = "slab" version = "0.4.9" @@ -4726,9 +4679,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "snow" @@ -4743,15 +4696,15 @@ dependencies = [ "rand_core 0.6.4", "ring", "rustc_version", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", ] [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4805,7 +4758,7 @@ dependencies = [ "num-integer", "num-traits", "rfc6979", - "sha2 0.10.8", + "sha2 0.10.9", "starknet-curve", "starknet-types-core", "zeroize", @@ -4822,9 +4775,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" +checksum = "4037bcb26ce7c508448d221e570d075196fd4f6912ae6380981098937af9522a" dependencies = [ "lambdaworks-crypto", "lambdaworks-math", @@ -4832,6 +4785,8 @@ dependencies = [ "num-integer", "num-traits", "serde", + "size-of", + "zeroize", ] [[package]] @@ -4882,9 +4837,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -4899,9 +4854,9 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -4956,9 +4911,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", - "rustix 1.0.2", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -5049,9 +5004,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -5064,15 +5019,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -5080,9 +5035,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -5145,9 +5100,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -5158,9 +5113,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -5179,11 +5134,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.25" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap 2.8.0", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -5193,9 +5148,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tower" @@ -5401,12 +5356,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -5421,11 +5370,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -5467,9 +5416,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -5560,9 +5509,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -5571,9 +5520,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -5626,15 +5575,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.53.0" @@ -5651,13 +5591,26 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -5669,6 +5622,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -5680,11 +5644,22 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" @@ -5704,6 +5679,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -5714,6 +5698,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -5864,9 +5857,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -5883,24 +5876,18 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "x25519-dalek" @@ -5933,9 +5920,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmltree" @@ -5994,9 +5981,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -6006,9 +5993,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -6018,38 +6005,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", @@ -6097,11 +6064,22 @@ dependencies = [ "syn", ] +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -6110,9 +6088,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/code/crates/config/src/lib.rs b/code/crates/config/src/lib.rs index fa0f01a16..28f36654b 100644 --- a/code/crates/config/src/lib.rs +++ b/code/crates/config/src/lib.rs @@ -539,6 +539,11 @@ pub struct TimeoutConfig { /// the vote synchronization protocol. #[serde(with = "humantime_serde")] pub timeout_step: Duration, + + /// How long we wait after entering a round before starting + /// the rebroadcast liveness protocol + #[serde(with = "humantime_serde")] + pub timeout_rebroadcast: Duration, } impl TimeoutConfig { @@ -549,8 +554,9 @@ impl TimeoutConfig { TimeoutKind::Precommit => self.timeout_precommit, TimeoutKind::PrevoteTimeLimit => self.timeout_step, TimeoutKind::PrecommitTimeLimit => self.timeout_step, - TimeoutKind::PrevoteRebroadcast => self.timeout_prevote, - TimeoutKind::PrecommitRebroadcast => self.timeout_precommit, + TimeoutKind::Rebroadcast => { + self.timeout_propose + self.timeout_prevote + self.timeout_precommit + } } } @@ -561,22 +567,28 @@ impl TimeoutConfig { TimeoutKind::Precommit => Some(self.timeout_precommit_delta), TimeoutKind::PrevoteTimeLimit => None, TimeoutKind::PrecommitTimeLimit => None, - TimeoutKind::PrevoteRebroadcast => None, - TimeoutKind::PrecommitRebroadcast => None, + TimeoutKind::Rebroadcast => None, } } } impl Default for TimeoutConfig { fn default() -> Self { + let timeout_propose = Duration::from_secs(3); + let timeout_prevote = Duration::from_secs(1); + let timeout_precommit = Duration::from_secs(1); + let timeout_step = Duration::from_secs(2); + let timeout_rebroadcast = timeout_propose + timeout_prevote + timeout_precommit; + Self { - timeout_propose: Duration::from_secs(3), + timeout_propose, timeout_propose_delta: Duration::from_millis(500), - timeout_prevote: Duration::from_secs(1), + timeout_prevote, timeout_prevote_delta: Duration::from_millis(500), - timeout_precommit: Duration::from_secs(1), + timeout_precommit, timeout_precommit_delta: Duration::from_millis(500), - timeout_step: Duration::from_secs(2), + timeout_step, + timeout_rebroadcast, } } } diff --git a/code/crates/core-consensus/src/effect.rs b/code/crates/core-consensus/src/effect.rs index b0c2425c6..a0d2492a9 100644 --- a/code/crates/core-consensus/src/effect.rs +++ b/code/crates/core-consensus/src/effect.rs @@ -3,7 +3,7 @@ use derive_where::derive_where; use malachitebft_core_types::*; use crate::input::RequestId; -use crate::types::SignedConsensusMsg; +use crate::types::{LivenessMsg, SignedConsensusMsg}; use crate::{ConsensusMsg, VoteExtensionError, WalEntry}; /// Provides a way to construct the appropriate [`Resume`] value to @@ -82,12 +82,22 @@ where /// Publish a message to peers /// /// Resume with: [`resume::Continue`] - Publish(SignedConsensusMsg, resume::Continue), + PublishConsensusMsg(SignedConsensusMsg, resume::Continue), + + /// Publish a liveness message to peers + /// + /// Resume with: [`resume::Continue`] + PublishLivenessMsg(LivenessMsg, resume::Continue), /// Rebroadcast a vote to peers /// /// Resume with: [`resume::Continue`] - Rebroadcast(SignedVote, resume::Continue), + RebroadcastVote(SignedVote, resume::Continue), + + /// Rebroadcast a round certificate to peers + /// + /// Resume with: [`resume::Continue`] + RebroadcastRoundCertificate(RoundCertificate, resume::Continue), /// Requests the application to build a value for consensus to run on. /// diff --git a/code/crates/core-consensus/src/handle.rs b/code/crates/core-consensus/src/handle.rs index 95be21631..e7bb336f6 100644 --- a/code/crates/core-consensus/src/handle.rs +++ b/code/crates/core-consensus/src/handle.rs @@ -1,5 +1,6 @@ mod decide; mod driver; +mod liveness; mod proposal; mod propose; mod proposed_value; @@ -13,6 +14,7 @@ mod validator_set; mod vote; mod vote_set; +use liveness::{on_polka_certificate, on_round_certificate}; use proposal::on_proposal; use propose::on_propose; use proposed_value::on_proposed_value; @@ -67,5 +69,11 @@ where Input::VoteSetResponse(vote_set, polka_certificate) => { on_vote_set_response(co, state, metrics, vote_set, polka_certificate).await } + Input::PolkaCertificate(certificate) => { + on_polka_certificate(co, state, metrics, certificate).await + } + Input::RoundCertificate(certificate) => { + on_round_certificate(co, state, metrics, certificate).await + } } } diff --git a/code/crates/core-consensus/src/handle/driver.rs b/code/crates/core-consensus/src/handle/driver.rs index a9a014be8..9cfe5fe05 100644 --- a/code/crates/core-consensus/src/handle/driver.rs +++ b/code/crates/core-consensus/src/handle/driver.rs @@ -6,8 +6,9 @@ use crate::handle::on_proposal; use crate::handle::signature::sign_proposal; use crate::handle::signature::sign_vote; use crate::handle::vote::on_vote; +use crate::params::HIDDEN_LOCK_ROUND; use crate::prelude::*; -use crate::types::SignedConsensusMsg; +use crate::types::{LivenessMsg, SignedConsensusMsg}; use crate::util::pretty::PrettyVal; use crate::LocallyProposedValue; use crate::VoteSyncMode; @@ -29,13 +30,50 @@ where #[cfg(feature = "metrics")] metrics.round.set(round.as_i64()); + // Publishing the round certificate upon entering round > 0 + // is part of the new round synchronization mechanism, which + // ensures all validators advance through rounds even in the + // presence of asynchrony or Byzantine behavior. Moreover, + // it guarantees that after GST, all correct replicas will receive + // the round certificate and enter the same round within bounded time. + if round > &Round::new(0) { + if let Some(cert) = state.driver.round_certificate() { + if cert.enter_round == *round { + info!( + %cert.certificate.height, + %cert.enter_round, + number_of_votes = cert.certificate.round_signatures.len(), + "Sending round certificate" + ); + perform!( + co, + Effect::PublishLivenessMsg( + LivenessMsg::SkipRoundCertificate(cert.certificate.clone()), + Default::default() + ) + ); + } + } + } + info!(%height, %round, %proposer, "Starting new round"); + state.last_signed_prevote = None; + state.last_signed_precommit = None; perform!(co, Effect::CancelAllTimeouts(Default::default())); perform!( co, Effect::StartRound(*height, *round, proposer.clone(), Default::default()) ); + + #[cfg(feature = "metrics")] + metrics.rebroadcast_timeouts.inc(); + + // Schedule rebroadcast timer if necessary + if state.params.vote_sync_mode == VoteSyncMode::Rebroadcast { + let timeout = Timeout::rebroadcast(*round); + perform!(co, Effect::ScheduleTimeout(timeout, Default::default())); + } } DriverInput::ProposeValue(round, _) => { @@ -227,7 +265,7 @@ where // Only sign and publish if we're in the validator set if state.is_validator() { - let signed_proposal = sign_proposal(co, proposal).await?; + let signed_proposal = sign_proposal(co, proposal.clone()).await?; if signed_proposal.pol_round().is_defined() { perform!( @@ -250,18 +288,101 @@ where if state.params.value_payload.include_proposal() { perform!( co, - Effect::Publish( + Effect::PublishConsensusMsg( SignedConsensusMsg::Proposal(signed_proposal), Default::default() ) ); }; + + // Publishing the polka certificate of the re-proposed value + // ensures all validators receive it, which is necessary for + // them to accept the re-proposed value. + if proposal.pol_round().is_defined() { + // Broadcast the polka certificate at pol_round + let Some(polka_certificate) = + state.polka_certificate_at_round(proposal.pol_round()) + else { + panic!( + "Missing polka certificate for pol_round {}", + proposal.pol_round() + ); + }; + perform!( + co, + Effect::PublishLivenessMsg( + LivenessMsg::PolkaCertificate(polka_certificate), + Default::default() + ) + ); + } } Ok(()) } DriverOutput::Vote(vote) => { + // Upon locking, in addition to publishing a Precommit message, + // a validator must request the application to restream the proposal, + // publish the proposal message, and publish the polka certificate. + // In other words, it must ensure that all validators receive the same events + // that led it to lock a value. Together with the timeout mechanisms, + // this guarantees that after GST, all correct validators will update + // their validValue and validRound to these values in this round. + // As a result, Malachite ensures liveness, because all validators + // will be aware of the most recently locked value, and whichever validator + // becomes the leader in one of the following rounds will propose a value + // that all correct validators can accept. + // Importantly, this mechanism does not need to be enabled from round 0, + // as it is expensive; it can be activated from any round as a last-resort + // backup to guarantee liveness. + if vote.vote_type() == VoteType::Precommit + && vote.value().is_val() + && state.driver.round() >= HIDDEN_LOCK_ROUND + { + if let Some((signed_proposal, Validity::Valid)) = + state.driver.proposal_and_validity_for_round(vote.round()) + { + perform!( + co, + Effect::RestreamProposal( + signed_proposal.height(), + signed_proposal.round(), + signed_proposal.pol_round(), + signed_proposal.validator_address().clone(), + signed_proposal.value().id(), + Default::default() + ) + ); + + if state.params.value_payload.include_proposal() { + perform!( + co, + Effect::PublishConsensusMsg( + SignedConsensusMsg::Proposal(signed_proposal.clone()), + Default::default() + ) + ); + } + + let Some(polka_certificate) = state.polka_certificate_at_round(vote.round()) + else { + panic!( + "Missing polka certificate for Precommit({:?}) at round {}", + vote.value(), + vote.round() + ); + }; + perform!( + co, + Effect::PublishLivenessMsg( + LivenessMsg::PolkaCertificate(polka_certificate), + Default::default() + ) + ); + } + } + if state.is_validator() { info!( vote_type = ?vote.vote_type(), @@ -270,8 +391,6 @@ where "Voting", ); - let vote_type = vote.vote_type(); - let extended_vote = extend_vote(co, vote).await?; let signed_vote = sign_vote(co, extended_vote).await?; @@ -279,23 +398,13 @@ where perform!( co, - Effect::Publish( + Effect::PublishConsensusMsg( SignedConsensusMsg::Vote(signed_vote.clone()), Default::default() ) ); state.set_last_vote(signed_vote); - - // Schedule rebroadcast timer if necessary - if state.params.vote_sync_mode == VoteSyncMode::Rebroadcast { - let timeout = match vote_type { - VoteType::Prevote => Timeout::prevote_rebroadcast(state.driver.round()), - VoteType::Precommit => Timeout::precommit_rebroadcast(state.driver.round()), - }; - - perform!(co, Effect::ScheduleTimeout(timeout, Default::default())); - } } Ok(()) diff --git a/code/crates/core-consensus/src/handle/liveness.rs b/code/crates/core-consensus/src/handle/liveness.rs new file mode 100644 index 000000000..1c7348db1 --- /dev/null +++ b/code/crates/core-consensus/src/handle/liveness.rs @@ -0,0 +1,155 @@ +use crate::handle::validator_set::get_validator_set; +use crate::handle::{driver::apply_driver_input, vote::verify_signed_vote}; +use crate::prelude::*; + +use super::signature::verify_polka_certificate; + +/// Handles the processing of a polka certificate. +/// +/// This function is responsible for: +/// 1. Validating that the certificate's height matches the current state height +/// 2. Retrieving and verifying the validator set for the given height +/// 3. Verifying the polka certificate's validity using the validator set +/// 4. Applying the certificate to the consensus state if valid +/// +/// Note: The certificate is sent to the driver as a single input to make sure a +/// `ProposalAndPolka...` input is generated and sent to the state machine +/// even in the presence of equivocating votes. +/// +/// # Returns +/// * `Result<(), Error>` - Ok(()) if processing completed successfully (even if certificate was invalid), +/// or an error if there was a problem processing the certificate +pub async fn on_polka_certificate( + co: &Co, + state: &mut State, + metrics: &Metrics, + certificate: PolkaCertificate, +) -> Result<(), Error> +where + Ctx: Context, +{ + info!(%certificate.height, %certificate.round, "Received polka certificate"); + + if certificate.height != state.height() { + warn!( + %certificate.height, + consensus.height = %state.height(), + "Polka certificate height mismatch" + ); + + return Ok(()); + } + + let validator_set = get_validator_set(co, state, certificate.height) + .await? + .ok_or_else(|| Error::ValidatorSetNotFound(certificate.height))?; + + let validity = verify_polka_certificate( + co, + certificate.clone(), + validator_set.into_owned(), + state.params.threshold_params, + ) + .await?; + + if let Err(e) = validity { + warn!(?certificate, "Invalid polka certificate: {e}"); + return Ok(()); + } + + apply_driver_input( + co, + state, + metrics, + DriverInput::PolkaCertificate(certificate), + ) + .await +} + +/// Handles the processing of a round certificate. +/// +/// This function is responsible for: +/// 1. Validating that the certificate's height matches the current state height +/// 2. Processing each vote signature in the round certificate +/// 3. Converting signatures into appropriate vote types (Prevote or Precommit) +/// 4. Verifying each vote's validity +/// 5. Applying valid votes to the consensus state +/// +/// Note: The round certificate can be of type `2f+1` PrecommitAny or `f+1` SkipRound (*). +/// For round certificates, in contrast to polka certificates, the votes are applied +/// individually to the driver and once the threshold is reached it is sent to the state machine. +/// Presence of equivocating votes is not a problem, as the driver will ignore them while +/// the vote keeper will still be able to generate the threshold output using the existing +/// stored and incoming votes from the certificate. +/// +/// (*) There is currently no validation of the correctness of the certificate in this function. +/// A byzantine validator may send a certificate that does not have enough votes to reach the +/// thresholds for `PrecommitAny` or `SkipRound`. +/// +/// # Returns +/// * `Result<(), Error>` - Ok(()) if processing completed successfully, +/// or an error if there was a problem processing the certificate +pub async fn on_round_certificate( + co: &Co, + state: &mut State, + metrics: &Metrics, + certificate: RoundCertificate, +) -> Result<(), Error> +where + Ctx: Context, +{ + info!( + %certificate.height, + %certificate.round, + "Received round certificate" + ); + + if certificate.height != state.height() { + warn!( + %certificate.height, + consensus.height = %state.height(), + "Round certificate height mismatch" + ); + + return Ok(()); + } + + for signature in certificate.round_signatures { + let vote_type = signature.vote_type; + let vote: SignedVote = match vote_type { + VoteType::Prevote => SignedVote::new( + state.ctx.new_prevote( + certificate.height, + certificate.round, + signature.value_id, + signature.address, + ), + signature.signature, + ), + VoteType::Precommit => SignedVote::new( + state.ctx.new_precommit( + certificate.height, + certificate.round, + signature.value_id, + signature.address, + ), + signature.signature, + ), + }; + + if !verify_signed_vote(co, state, &vote).await? { + warn!(?vote, "Invalid vote"); + continue; + } + apply_driver_input(co, state, metrics, DriverInput::Vote(vote)).await?; + } + + // Cancel rebroadcast timer + // TODO: Should do only if the round certificate is well formed, i.e. either PrecommitAny or SkipRound + perform!( + co, + Effect::CancelTimeout(Timeout::rebroadcast(certificate.round), Default::default()) + ); + + Ok(()) +} diff --git a/code/crates/core-consensus/src/handle/rebroadcast_timeout.rs b/code/crates/core-consensus/src/handle/rebroadcast_timeout.rs index 53fd405a3..60f34ae2c 100644 --- a/code/crates/core-consensus/src/handle/rebroadcast_timeout.rs +++ b/code/crates/core-consensus/src/handle/rebroadcast_timeout.rs @@ -5,7 +5,6 @@ pub async fn on_rebroadcast_timeout( co: &Co, state: &mut State, metrics: &Metrics, - timeout: Timeout, ) -> Result<(), Error> where Ctx: Context, @@ -16,31 +15,52 @@ where let (height, round) = (state.driver.height(), state.driver.round()); - let (maybe_vote, timeout) = match timeout.kind { - TimeoutKind::PrevoteRebroadcast => ( - state.last_signed_prevote.as_ref(), - Timeout::prevote_rebroadcast(round), - ), - TimeoutKind::PrecommitRebroadcast => ( - state.last_signed_precommit.as_ref(), - Timeout::precommit_rebroadcast(round), - ), - _ => return Ok(()), + if let Some(vote) = state.last_signed_prevote.as_ref() { + warn!( + %height, %round, vote_height = %vote.height(), vote_round = %vote.round(), + "Rebroadcasting vote at {:?} step", + state.driver.step() + ); + + perform!( + co, + Effect::RebroadcastVote(vote.clone(), Default::default()) + ); }; - if let Some(vote) = maybe_vote.cloned() { + if let Some(vote) = state.last_signed_precommit.as_ref() { warn!( - %height, %round, - "Rebroadcasting vote at {:?} step after {:?} timeout", - state.driver.step(), timeout.kind, + %height, %round, vote_height = %vote.height(), vote_round = %vote.round(), + "Rebroadcasting vote at {:?} step", + state.driver.step() + ); + perform!( + co, + Effect::RebroadcastVote(vote.clone(), Default::default()) ); + }; - perform!(co, Effect::Rebroadcast(vote, Default::default())); - perform!(co, Effect::ScheduleTimeout(timeout, Default::default())); - } + if let Some(cert) = state.round_certificate() { + if cert.enter_round == round { + warn!( + %cert.certificate.height, + %round, + %cert.certificate.round, + number_of_votes = cert.certificate.round_signatures.len(), + "Rebroadcasting round certificate" + ); + perform!( + co, + Effect::RebroadcastRoundCertificate(cert.certificate.clone(), Default::default()) + ); + } + }; #[cfg(feature = "metrics")] metrics.rebroadcast_timeouts.inc(); + let timeout = Timeout::rebroadcast(round); + perform!(co, Effect::ScheduleTimeout(timeout, Default::default())); + Ok(()) } diff --git a/code/crates/core-consensus/src/handle/timeout.rs b/code/crates/core-consensus/src/handle/timeout.rs index 8610b2d86..79df7d0e0 100644 --- a/code/crates/core-consensus/src/handle/timeout.rs +++ b/code/crates/core-consensus/src/handle/timeout.rs @@ -50,9 +50,7 @@ where apply_driver_input(co, state, metrics, DriverInput::TimeoutElapsed(timeout)).await?; match timeout.kind { - TimeoutKind::PrevoteRebroadcast | TimeoutKind::PrecommitRebroadcast => { - on_rebroadcast_timeout(co, state, metrics, timeout).await - } + TimeoutKind::Rebroadcast => on_rebroadcast_timeout(co, state, metrics).await, TimeoutKind::PrevoteTimeLimit | TimeoutKind::PrecommitTimeLimit => { on_step_limit_timeout(co, state, metrics, timeout.round).await } diff --git a/code/crates/core-consensus/src/input.rs b/code/crates/core-consensus/src/input.rs index 884765628..198e40e03 100644 --- a/code/crates/core-consensus/src/input.rs +++ b/code/crates/core-consensus/src/input.rs @@ -1,7 +1,7 @@ use derive_where::derive_where; use malachitebft_core_types::{ - CommitCertificate, Context, PolkaCertificate, Round, SignedProposal, SignedVote, Timeout, - ValueOrigin, VoteSet, + CommitCertificate, Context, PolkaCertificate, Round, RoundCertificate, SignedProposal, + SignedVote, Timeout, ValueOrigin, VoteSet, }; use crate::types::ProposedValue; @@ -27,6 +27,12 @@ where /// i.e. when consensus runs in a mode where the proposer sends a Proposal consensus message over the network. Proposal(SignedProposal), + /// Process a PolkaCertificate message received over the network + PolkaCertificate(PolkaCertificate), + + /// Process a RoundCertificate message received over the network + RoundCertificate(RoundCertificate), + /// Propose the given value. /// /// This input MUST only be provided when we are the proposer for the current round. diff --git a/code/crates/core-consensus/src/lib.rs b/code/crates/core-consensus/src/lib.rs index 8c7c94dce..5a46d9caf 100644 --- a/code/crates/core-consensus/src/lib.rs +++ b/code/crates/core-consensus/src/lib.rs @@ -12,6 +12,9 @@ pub use error::Error; mod params; pub use params::{Params, ThresholdParams, VoteSyncMode}; +#[doc(hidden)] +pub use params::HIDDEN_LOCK_ROUND; + mod effect; pub use effect::{Effect, Resumable, Resume}; diff --git a/code/crates/core-consensus/src/params.rs b/code/crates/core-consensus/src/params.rs index 754cfb14f..2ea3aa326 100644 --- a/code/crates/core-consensus/src/params.rs +++ b/code/crates/core-consensus/src/params.rs @@ -1,7 +1,11 @@ use derive_where::derive_where; -use malachitebft_core_types::{Context, ValuePayload}; +use malachitebft_core_types::{Context, Round, ValuePayload}; +/// The round from which we enable the hidden lock mitigation mechanism +pub const HIDDEN_LOCK_ROUND: Round = Round::new(10); + +#[doc(inline)] pub use malachitebft_core_driver::ThresholdParams; /// Consensus parameters. diff --git a/code/crates/core-consensus/src/state.rs b/code/crates/core-consensus/src/state.rs index 214963132..16a8f5af8 100644 --- a/code/crates/core-consensus/src/state.rs +++ b/code/crates/core-consensus/src/state.rs @@ -143,6 +143,15 @@ where Some((votes, certificates)) } + pub fn polka_certificate_at_round(&self, round: Round) -> Option> { + // Get the polka certificate for the specified round if it exists + self.driver + .polka_certificates() + .iter() + .find(|c| c.round == round && c.height == self.driver.height()) + .cloned() + } + pub fn full_proposal_at_round_and_value( &self, height: &Ctx::Height, @@ -260,6 +269,10 @@ where .get_by_address(self.address()) .is_some() } + + pub fn round_certificate(&self) -> Option<&EnterRoundCertificate> { + self.driver.round_certificate.as_ref() + } } fn round_range_inclusive(from: Round, to: Round) -> Box> { diff --git a/code/crates/core-consensus/src/types.rs b/code/crates/core-consensus/src/types.rs index 56184592f..039daca6a 100644 --- a/code/crates/core-consensus/src/types.rs +++ b/code/crates/core-consensus/src/types.rs @@ -2,7 +2,8 @@ use derive_where::derive_where; use thiserror::Error; use malachitebft_core_types::{ - Context, Proposal, Round, Signature, SignedProposal, SignedVote, Timeout, Validity, Vote, + Context, PolkaCertificate, Proposal, Round, RoundCertificate, Signature, SignedProposal, + SignedVote, Timeout, Validity, Vote, }; pub use malachitebft_core_types::ValuePayload; @@ -114,3 +115,10 @@ pub enum VoteExtensionError { #[error("Invalid vote extension")] InvalidVoteExtension, } + +#[derive_where(Clone, Debug, PartialEq, Eq)] +pub enum LivenessMsg { + Vote(SignedVote), + PolkaCertificate(PolkaCertificate), + SkipRoundCertificate(RoundCertificate), +} diff --git a/code/crates/core-driver/src/driver.rs b/code/crates/core-driver/src/driver.rs index 3853a147b..4df91617f 100644 --- a/code/crates/core-driver/src/driver.rs +++ b/code/crates/core-driver/src/driver.rs @@ -1,3 +1,4 @@ +use alloc::collections::BTreeSet; use alloc::vec; use alloc::vec::Vec; use core::fmt; @@ -7,9 +8,9 @@ use malachitebft_core_state_machine::output::Output as RoundOutput; use malachitebft_core_state_machine::state::{RoundValue, State as RoundState, Step}; use malachitebft_core_state_machine::state_machine::Info; use malachitebft_core_types::{ - CommitCertificate, Context, NilOrVal, PolkaCertificate, PolkaSignature, Proposal, Round, - SignedProposal, SignedVote, Timeout, TimeoutKind, Validator, ValidatorSet, Validity, Value, - ValueId, Vote, VoteType, + CommitCertificate, Context, EnterRoundCertificate, NilOrVal, PolkaCertificate, PolkaSignature, + Proposal, Round, SignedProposal, SignedVote, Timeout, TimeoutKind, Validator, ValidatorSet, + Validity, Value, ValueId, Vote, VoteType, }; use malachitebft_core_votekeeper::keeper::Output as VKOutput; use malachitebft_core_votekeeper::keeper::VoteKeeper; @@ -63,6 +64,9 @@ where last_prevote: Option, last_precommit: Option, + + /// The certificate that justifies moving to the `enter_round` specified in the `EnterRoundCertificate. + pub round_certificate: Option>, } impl Driver @@ -98,6 +102,7 @@ where polka_certificates: vec![], last_prevote: None, last_precommit: None, + round_certificate: None, } } @@ -227,6 +232,11 @@ where &self.polka_certificates } + /// Get the round certificate for the current round. + pub fn round_certificate(&self) -> Option<&EnterRoundCertificate> { + self.round_certificate.as_ref() + } + /// Get the proposal for the given round. pub fn proposal_and_validity_for_round( &self, @@ -453,8 +463,11 @@ where return Ok(None); }; - if let VKOutput::PolkaValue(val) = &output { - self.store_polka_certificate(vote_round, val); + match &output { + VKOutput::PolkaValue(val) => self.store_polka_certificate(vote_round, val), + VKOutput::PrecommitAny => self.store_precommit_any_round_certificate(vote_round), + VKOutput::SkipRound(round) => self.store_skip_round_certificate(*round), + _ => (), } let (input_round, round_input) = self.multiplex_vote_threshold(output, vote_round); @@ -487,6 +500,47 @@ where }) } + fn store_precommit_any_round_certificate(&mut self, vote_round: Round) { + let Some(per_round) = self.vote_keeper.per_round(vote_round) else { + panic!("Missing the PrecommitAny votes for round {}", vote_round); + }; + + let precommits: Vec> = per_round + .received_votes() + .iter() + .filter(|v| v.vote_type() == VoteType::Precommit) + .cloned() + .collect(); + + self.round_certificate = Some(EnterRoundCertificate::new_from_votes( + self.height(), + vote_round.increment(), + vote_round, + precommits, + )); + } + + fn store_skip_round_certificate(&mut self, vote_round: Round) { + let Some(per_round) = self.vote_keeper.per_round(vote_round) else { + panic!("Missing the SkipRoundvotes for round {}", vote_round); + }; + + let mut seen_addresses = BTreeSet::new(); + let skip_votes: Vec<_> = per_round + .received_votes() + .iter() + .filter(|vote| seen_addresses.insert(vote.validator_address())) + .cloned() + .collect(); + + self.round_certificate = Some(EnterRoundCertificate::new_from_votes( + self.height(), + vote_round, + vote_round, + skip_votes, + )); + } + fn apply_timeout(&mut self, timeout: Timeout) -> Result>, Error> { let input = match timeout.kind { TimeoutKind::Propose => RoundInput::TimeoutPropose, @@ -496,8 +550,7 @@ where // The driver never receives these events, so we can just ignore them. TimeoutKind::PrevoteTimeLimit => return Ok(None), TimeoutKind::PrecommitTimeLimit => return Ok(None), - TimeoutKind::PrevoteRebroadcast => return Ok(None), - TimeoutKind::PrecommitRebroadcast => return Ok(None), + TimeoutKind::Rebroadcast => return Ok(None), }; self.apply_input(timeout.round, input) diff --git a/code/crates/core-driver/src/lib.rs b/code/crates/core-driver/src/lib.rs index 7bfe463b6..bfad196da 100644 --- a/code/crates/core-driver/src/lib.rs +++ b/code/crates/core-driver/src/lib.rs @@ -10,7 +10,7 @@ )] // no_std compatibility #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] +#![cfg_attr(not(test), deny(clippy::unwrap_used))] #![cfg_attr(coverage_nightly, feature(coverage_attribute))] extern crate alloc; diff --git a/code/crates/core-state-machine/src/traces/line.rs b/code/crates/core-state-machine/src/traces/line.rs index d794b75fa..90b9447b8 100644 --- a/code/crates/core-state-machine/src/traces/line.rs +++ b/code/crates/core-state-machine/src/traces/line.rs @@ -78,10 +78,10 @@ pub enum Line { /// L55 - f+1 for higher round: move to that round L55, - /// L59 - proposer + /// L59 - proposer, proposeTimeout expired: prevote nil L59Proposer, - /// L59 - non proposer + /// L59 - non proposer, proposeTimeout expired: prevote nil L59NonProposer, /// L61 - prevoteTimeout expired: precommit nil diff --git a/code/crates/core-types/src/certificate.rs b/code/crates/core-types/src/certificate.rs index 6f0320437..d406ee1fa 100644 --- a/code/crates/core-types/src/certificate.rs +++ b/code/crates/core-types/src/certificate.rs @@ -166,3 +166,93 @@ pub enum CertificateError { #[error("Multiple votes from the same validator: {0}")] DuplicateVote(Ctx::Address), } + +/// Represents a signature for a round certificate, with the address of the validator that produced it. +#[derive_where(Clone, Debug, PartialEq, Eq)] +pub struct RoundSignature { + /// The vote type + pub vote_type: VoteType, + /// The value id + pub value_id: NilOrVal>, + /// The address associated with the signature. + pub address: Ctx::Address, + /// The signature itself. + pub signature: Signature, +} + +impl RoundSignature { + /// Create a new `CommitSignature` from an address and a signature. + pub fn new( + vote_type: VoteType, + value_id: NilOrVal>, + address: Ctx::Address, + signature: Signature, + ) -> Self { + Self { + vote_type, + value_id, + address, + signature, + } + } +} + +/// Represents a certificate for entering a new round at a given height. +#[derive_where(Clone, Debug, PartialEq, Eq)] +pub struct RoundCertificate { + /// The height at which a certificate was witnessed + pub height: Ctx::Height, + /// The round of the votes that made up the certificate + pub round: Round, + /// The signatures for the votes that make up the certificate + pub round_signatures: Vec>, +} + +impl RoundCertificate { + /// Creates a new `RoundCertificate` from a vector of signed votes. + pub fn new_from_votes(height: Ctx::Height, round: Round, votes: Vec>) -> Self { + RoundCertificate { + height, + round, + round_signatures: votes + .into_iter() + .map(|v| { + RoundSignature::new( + v.vote_type(), + v.value().clone(), + v.validator_address().clone(), + v.signature, + ) + }) + .collect(), + } + } +} + +/// Represents a local certificate that triggered or will trigger the start of a new round. +#[derive_where(Clone, Debug, PartialEq, Eq)] +pub struct EnterRoundCertificate { + /// The certificate that triggered or will trigger the start of a new round + pub certificate: RoundCertificate, + /// The round that will be entered due to the `RoundCertificate`. + /// - If the certificate is `PrecommitAny`, it contains signatures from the previous round, + /// so `enter_round` will be one more than the round of those signatures. + /// - If the certificate is `SkipRound`, it contains signatures from the round being entered, + /// so `enter_round` will be equal to the round of those signatures. + pub enter_round: Round, +} + +impl EnterRoundCertificate { + /// Creates a new `LocalRoundCertificate` from a vector of signed votes. + pub fn new_from_votes( + height: Ctx::Height, + enter_round: Round, + round: Round, + votes: Vec>, + ) -> Self { + Self { + certificate: RoundCertificate::new_from_votes(height, round, votes), + enter_round, + } + } +} diff --git a/code/crates/core-types/src/lib.rs b/code/crates/core-types/src/lib.rs index 9b0bd0c1a..bba4c18eb 100644 --- a/code/crates/core-types/src/lib.rs +++ b/code/crates/core-types/src/lib.rs @@ -54,7 +54,8 @@ pub type SignedProposalPart = SignedMessage::Proposal pub type SignedExtension = SignedMessage::Extension>; pub use certificate::{ - CertificateError, CommitCertificate, CommitSignature, PolkaCertificate, PolkaSignature, + CertificateError, CommitCertificate, CommitSignature, EnterRoundCertificate, PolkaCertificate, + PolkaSignature, RoundCertificate, RoundSignature, }; pub use context::Context; pub use height::Height; diff --git a/code/crates/core-types/src/timeout.rs b/code/crates/core-types/src/timeout.rs index 104410fad..21bf3719d 100644 --- a/code/crates/core-types/src/timeout.rs +++ b/code/crates/core-types/src/timeout.rs @@ -20,11 +20,8 @@ pub enum TimeoutKind { /// Timeout for detecting consensus being in the precommit step for too long. PrecommitTimeLimit, - /// Timeout to rebroadcast the last prevote - PrevoteRebroadcast, - - /// Timeout to rebroadcast the last precommit - PrecommitRebroadcast, + /// Timeout to rebroadcast the round synchronization messages + Rebroadcast, } /// A timeout for a round step. @@ -67,14 +64,9 @@ impl Timeout { Self::new(round, TimeoutKind::PrecommitTimeLimit) } - /// Create a new timeout for rebroadcasting the last prevote. - pub const fn prevote_rebroadcast(round: Round) -> Self { - Self::new(round, TimeoutKind::PrevoteRebroadcast) - } - - /// Create a new timeout for rebroadcasting the last precommit. - pub const fn precommit_rebroadcast(round: Round) -> Self { - Self::new(round, TimeoutKind::PrecommitRebroadcast) + /// Create a new timeout for rebroadcasting the round synchronization messages. + pub const fn rebroadcast(round: Round) -> Self { + Self::new(round, TimeoutKind::Rebroadcast) } } diff --git a/code/crates/engine/src/consensus.rs b/code/crates/engine/src/consensus.rs index ab3c2ce33..f4ee11913 100644 --- a/code/crates/engine/src/consensus.rs +++ b/code/crates/engine/src/consensus.rs @@ -14,7 +14,8 @@ use tracing::{debug, error, error_span, info, warn}; use malachitebft_codec as codec; use malachitebft_config::TimeoutConfig; use malachitebft_core_consensus::{ - Effect, PeerId, Resumable, Resume, SignedConsensusMsg, VoteExtensionError, VoteSyncMode, + Effect, LivenessMsg, PeerId, Resumable, Resume, SignedConsensusMsg, VoteExtensionError, + VoteSyncMode, }; use malachitebft_core_types::{ Context, Proposal, Round, SigningProvider, SigningProviderExt, Timeout, TimeoutKind, @@ -44,12 +45,14 @@ pub use malachitebft_core_consensus::State as ConsensusState; /// This trait is automatically implemented for any type that implements: /// - [`codec::Codec`] /// - [`codec::Codec>`] +/// - [`codec::Codec>`] /// - [`codec::Codec>`] pub trait ConsensusCodec where Ctx: Context, Self: codec::Codec, Self: codec::Codec>, + Self: codec::Codec>, Self: codec::Codec>, { } @@ -59,6 +62,7 @@ where Ctx: Context, Self: codec::Codec, Self: codec::Codec>, + Self: codec::Codec>, Self: codec::Codec>, { } @@ -186,8 +190,11 @@ impl Timeouts { TimeoutKind::Precommit => self.config.timeout_precommit, TimeoutKind::PrevoteTimeLimit => self.config.timeout_step, TimeoutKind::PrecommitTimeLimit => self.config.timeout_step, - TimeoutKind::PrevoteRebroadcast => self.config.timeout_prevote, - TimeoutKind::PrecommitRebroadcast => self.config.timeout_precommit, + TimeoutKind::Rebroadcast => { + self.config.timeout_propose + + self.config.timeout_prevote + + self.config.timeout_precommit + } } } @@ -199,8 +206,10 @@ impl Timeouts { TimeoutKind::Precommit => c.timeout_precommit += c.timeout_precommit_delta, TimeoutKind::PrevoteTimeLimit => (), TimeoutKind::PrecommitTimeLimit => (), - TimeoutKind::PrevoteRebroadcast => (), - TimeoutKind::PrecommitRebroadcast => (), + TimeoutKind::Rebroadcast => { + c.timeout_rebroadcast += + c.timeout_propose_delta + c.timeout_prevote_delta + c.timeout_precommit_delta + } }; } } @@ -580,6 +589,39 @@ where } } + NetworkEvent::PolkaCertificate(from, certificate) => { + if let Err(e) = self + .process_input( + &myself, + state, + ConsensusInput::PolkaCertificate(certificate), + ) + .await + { + error!(%from, "Error when processing polka certificate: {e}"); + } + } + + NetworkEvent::RoundCertificate(from, certificate) => { + info!( + %from, + %certificate.height, + %certificate.round, + number_of_votes = certificate.round_signatures.len(), + "Received round certificate" + ); + if let Err(e) = self + .process_input( + &myself, + state, + ConsensusInput::RoundCertificate(certificate), + ) + .await + { + error!(%from, "Error when processing round certificate: {e}"); + } + } + NetworkEvent::ProposalPart(from, part) => { if state.consensus.params.value_payload.proposal_only() { error!(%from, "Properly configured peer should never send proposal part messages in Proposal mode"); @@ -1057,7 +1099,7 @@ where Ok(r.resume_with(result)) } - Effect::Publish(msg, r) => { + Effect::PublishConsensusMsg(msg, r) => { // Sync the WAL to disk before we broadcast the message // NOTE: The message has already been append to the WAL by the `WalAppend` effect. self.wal_flush(state.phase).await?; @@ -1066,27 +1108,71 @@ where self.tx_event.send(|| Event::Published(msg.clone())); self.network - .cast(NetworkMsg::Publish(msg)) - .map_err(|e| eyre!("Error when broadcasting gossip message: {e:?}"))?; + .cast(NetworkMsg::PublishConsensusMsg(msg)) + .map_err(|e| eyre!("Error when broadcasting consensus message: {e:?}"))?; Ok(r.resume_with(())) } - Effect::Rebroadcast(msg, r) => { + Effect::PublishLivenessMsg(msg, r) => { + // Publish liveness message only if vote sync mode is set to "rebroadcast". + if self.params.vote_sync_mode == VoteSyncMode::Rebroadcast { + match msg { + LivenessMsg::Vote(ref msg) => { + self.tx_event.send(|| Event::RebroadcastVote(msg.clone())); + } + LivenessMsg::PolkaCertificate(ref certificate) => { + self.tx_event + .send(|| Event::PolkaCertificate(certificate.clone())); + } + LivenessMsg::SkipRoundCertificate(ref certificate) => { + self.tx_event + .send(|| Event::SkipRoundCertificate(certificate.clone())); + } + } + + self.network + .cast(NetworkMsg::PublishLivenessMsg(msg)) + .map_err(|e| eyre!("Error when broadcasting liveness message: {e:?}"))?; + } + + Ok(r.resume_with(())) + } + + Effect::RebroadcastVote(msg, r) => { // Rebroadcast last vote only if vote sync mode is set to "rebroadcast", // otherwise vote set requests are issued automatically by the sync protocol. if self.params.vote_sync_mode == VoteSyncMode::Rebroadcast { - // Notify any subscribers that we are about to rebroadcast a message - self.tx_event.send(|| Event::Rebroadcast(msg.clone())); + // Notify any subscribers that we are about to rebroadcast a vote + self.tx_event.send(|| Event::RebroadcastVote(msg.clone())); self.network - .cast(NetworkMsg::Publish(SignedConsensusMsg::Vote(msg))) + .cast(NetworkMsg::PublishLivenessMsg(LivenessMsg::Vote(msg))) .map_err(|e| eyre!("Error when rebroadcasting vote message: {e:?}"))?; } Ok(r.resume_with(())) } + Effect::RebroadcastRoundCertificate(certificate, r) => { + // Rebroadcast last round certificate only if vote sync mode is set to "rebroadcast". + if self.params.vote_sync_mode == VoteSyncMode::Rebroadcast { + // Notify any subscribers that we are about to rebroadcast a round certificate + self.tx_event + .send(|| Event::RebroadcastRoundCertificate(certificate.clone())); + + self.network + .cast(NetworkMsg::PublishLivenessMsg( + LivenessMsg::SkipRoundCertificate(certificate), + )) + .map_err(|e| { + eyre!("Error when rebroadcasting round certificate message: {e:?}") + })?; + } + + Ok(r.resume_with(())) + } + Effect::GetValue(height, round, timeout, r) => { let timeout_duration = state.timeouts.duration_for(timeout.kind); diff --git a/code/crates/engine/src/network.rs b/code/crates/engine/src/network.rs index e8226fc23..76348fa78 100644 --- a/code/crates/engine/src/network.rs +++ b/code/crates/engine/src/network.rs @@ -15,8 +15,10 @@ use malachitebft_sync::{ }; use malachitebft_codec as codec; -use malachitebft_core_consensus::SignedConsensusMsg; -use malachitebft_core_types::{Context, SignedProposal, SignedVote}; +use malachitebft_core_consensus::{LivenessMsg, SignedConsensusMsg}; +use malachitebft_core_types::{ + Context, PolkaCertificate, RoundCertificate, SignedProposal, SignedVote, +}; use malachitebft_metrics::SharedRegistry; use malachitebft_network::handle::CtrlHandle; use malachitebft_network::{Channel, Config, Event, Multiaddr, PeerId}; @@ -106,6 +108,10 @@ pub enum NetworkEvent { Proposal(PeerId, SignedProposal), ProposalPart(PeerId, StreamMessage), + PolkaCertificate(PeerId, PolkaCertificate), + + RoundCertificate(PeerId, RoundCertificate), + Status(PeerId, Status), Request(InboundRequestId, PeerId, Request), @@ -144,7 +150,10 @@ pub enum Msg { Subscribe(Box>>), /// Publish a signed consensus message - Publish(SignedConsensusMsg), + PublishConsensusMsg(SignedConsensusMsg), + + /// Publish a liveness message + PublishLivenessMsg(LivenessMsg), /// Publish a proposal part PublishProposalPart(StreamMessage), @@ -177,6 +186,7 @@ where Codec: codec::Codec>, Codec: codec::Codec>, Codec: codec::Codec>, + Codec: codec::Codec>, { type Msg = Msg; type State = State; @@ -250,9 +260,14 @@ where subscriber.subscribe_to_port(output_port); } - Msg::Publish(msg) => match self.codec.encode(&msg) { + Msg::PublishConsensusMsg(msg) => match self.codec.encode(&msg) { Ok(data) => ctrl_handle.publish(Channel::Consensus, data).await?, - Err(e) => error!("Failed to encode gossip message: {e:?}"), + Err(e) => error!("Failed to encode consensus message: {e:?}"), + }, + + Msg::PublishLivenessMsg(msg) => match self.codec.encode(&msg) { + Ok(data) => ctrl_handle.publish(Channel::Liveness, data).await?, + Err(e) => error!("Failed to encode liveness message: {e:?}"), }, Msg::PublishProposalPart(msg) => { @@ -328,11 +343,38 @@ where output_port.send(NetworkEvent::PeerDisconnected(peer_id)); } - Msg::NewEvent(Event::Message(Channel::Consensus, from, data)) => { + Msg::NewEvent(Event::LivenessMessage(Channel::Liveness, from, data)) => { let msg = match self.codec.decode(data) { Ok(msg) => msg, Err(e) => { - error!(%from, "Failed to decode gossip message: {e:?}"); + error!(%from, "Failed to decode liveness message: {e:?}"); + return Ok(()); + } + }; + + let event = match msg { + LivenessMsg::PolkaCertificate(polka_cert) => { + NetworkEvent::PolkaCertificate(from, polka_cert) + } + LivenessMsg::SkipRoundCertificate(round_cert) => { + NetworkEvent::RoundCertificate(from, round_cert) + } + LivenessMsg::Vote(vote) => NetworkEvent::Vote(from, vote), + }; + + output_port.send(event); + } + + Msg::NewEvent(Event::LivenessMessage(channel, from, _)) => { + error!(%from, "Unexpected liveness message on {channel} channel"); + return Ok(()); + } + + Msg::NewEvent(Event::ConsensusMessage(Channel::Consensus, from, data)) => { + let msg = match self.codec.decode(data) { + Ok(msg) => msg, + Err(e) => { + error!(%from, "Failed to decode consensus message: {e:?}"); return Ok(()); } }; @@ -347,7 +389,7 @@ where output_port.send(event); } - Msg::NewEvent(Event::Message(Channel::ProposalParts, from, data)) => { + Msg::NewEvent(Event::ConsensusMessage(Channel::ProposalParts, from, data)) => { let msg: StreamMessage = match self.codec.decode(data) { Ok(stream_msg) => stream_msg, Err(e) => { @@ -366,7 +408,7 @@ where output_port.send(NetworkEvent::ProposalPart(from, msg)); } - Msg::NewEvent(Event::Message(Channel::Sync, from, data)) => { + Msg::NewEvent(Event::ConsensusMessage(Channel::Sync, from, data)) => { let status: sync::Status = match self.codec.decode(data) { Ok(status) => status, Err(e) => { @@ -388,6 +430,11 @@ where )); } + Msg::NewEvent(Event::ConsensusMessage(channel, from, _)) => { + error!(%from, "Unexpected consensus message on {channel} channel"); + return Ok(()); + } + Msg::NewEvent(Event::Sync(raw_msg)) => match raw_msg { RawMessage::Request { request_id, diff --git a/code/crates/engine/src/util/events.rs b/code/crates/engine/src/util/events.rs index 0df9e166a..47cca2553 100644 --- a/code/crates/engine/src/util/events.rs +++ b/code/crates/engine/src/util/events.rs @@ -8,7 +8,9 @@ use tokio::sync::broadcast; use malachitebft_core_consensus::{ LocallyProposedValue, ProposedValue, SignedConsensusMsg, WalEntry, }; -use malachitebft_core_types::{CommitCertificate, Context, Round, SignedVote, ValueOrigin}; +use malachitebft_core_types::{ + CommitCertificate, Context, PolkaCertificate, Round, RoundCertificate, SignedVote, ValueOrigin, +}; pub type RxEvent = broadcast::Receiver>; @@ -48,7 +50,10 @@ pub enum Event { ProposedValue(LocallyProposedValue), ReceivedProposedValue(ProposedValue, ValueOrigin), Decided(CommitCertificate), - Rebroadcast(SignedVote), + RebroadcastVote(SignedVote), + RebroadcastRoundCertificate(RoundCertificate), + SkipRoundCertificate(RoundCertificate), + PolkaCertificate(PolkaCertificate), RequestedVoteSet(Ctx::Height, Round), SentVoteSetResponse(Ctx::Height, Round, usize, usize), WalReplayBegin(Ctx::Height, usize), @@ -75,7 +80,11 @@ impl fmt::Display for Event { ) } Event::Decided(cert) => write!(f, "Decided(value: {})", cert.value_id), - Event::Rebroadcast(msg) => write!(f, "Rebroadcast(msg: {msg:?})"), + Event::RebroadcastVote(vote) => write!(f, "RebroadcastVote(vote: {vote:?})"), + Event::RebroadcastRoundCertificate(certificate) => write!( + f, + "RebroadcastRoundCertificate(certificate: {certificate:?})" + ), Event::RequestedVoteSet(height, round) => { write!(f, "RequestedVoteSet(height: {height}, round: {round})") } @@ -91,6 +100,12 @@ impl fmt::Display for Event { Event::WalReplayEntry(entry) => write!(f, "WalReplayEntry(entry: {entry:?})"), Event::WalReplayDone(height) => write!(f, "WalReplayDone(height: {height})"), Event::WalReplayError(error) => write!(f, "WalReplayError({error})"), + Event::PolkaCertificate(certificate) => { + write!(f, "PolkaCertificate: {certificate:?})") + } + Event::SkipRoundCertificate(certificate) => { + write!(f, "SkipRoundCertificate: {certificate:?})") + } } } } diff --git a/code/crates/engine/src/wal/entry.rs b/code/crates/engine/src/wal/entry.rs index 70523822b..04e952959 100644 --- a/code/crates/engine/src/wal/entry.rs +++ b/code/crates/engine/src/wal/entry.rs @@ -125,8 +125,7 @@ fn encode_timeout(tag: u8, timeout: &Timeout, mut buf: impl Write) -> io::Result // but we still need to handle them here. TimeoutKind::PrevoteTimeLimit => 5, TimeoutKind::PrecommitTimeLimit => 6, - TimeoutKind::PrevoteRebroadcast => 7, - TimeoutKind::PrecommitRebroadcast => 8, + TimeoutKind::Rebroadcast => 7, }; buf.write_u8(tag)?; @@ -157,8 +156,7 @@ fn decode_timeout(mut buf: impl Read) -> io::Result { // but we still need to handle them here. 5 => TimeoutKind::PrevoteTimeLimit, 6 => TimeoutKind::PrecommitTimeLimit, - 7 => TimeoutKind::PrevoteRebroadcast, - 8 => TimeoutKind::PrecommitRebroadcast, + 7 => TimeoutKind::Rebroadcast, _ => { return Err(io::Error::new( diff --git a/code/crates/network/src/channel.rs b/code/crates/network/src/channel.rs index 97b7f4c30..605e4734f 100644 --- a/code/crates/network/src/channel.rs +++ b/code/crates/network/src/channel.rs @@ -7,17 +7,27 @@ use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Channel { Consensus, + Liveness, ProposalParts, Sync, } impl Channel { pub fn all() -> &'static [Channel] { - &[Channel::Consensus, Channel::ProposalParts, Channel::Sync] + &[ + Channel::Consensus, + Channel::ProposalParts, + Channel::Sync, + Channel::Liveness, + ] } pub fn consensus() -> &'static [Channel] { - &[Channel::Consensus, Channel::ProposalParts] + &[ + Channel::Consensus, + Channel::ProposalParts, + Channel::Liveness, + ] } pub fn to_gossipsub_topic(self) -> gossipsub::IdentTopic { @@ -33,6 +43,7 @@ impl Channel { Channel::Consensus => "/consensus", Channel::ProposalParts => "/proposal_parts", Channel::Sync => "/sync", + Channel::Liveness => "/liveness", } } @@ -53,6 +64,7 @@ impl Channel { "/consensus" => Some(Channel::Consensus), "/proposal_parts" => Some(Channel::ProposalParts), "/sync" => Some(Channel::Sync), + "/liveness" => Some(Channel::Liveness), _ => None, } } @@ -62,6 +74,7 @@ impl Channel { b"/consensus" => Some(Channel::Consensus), b"/proposal_parts" => Some(Channel::ProposalParts), b"/sync" => Some(Channel::Sync), + b"/liveness" => Some(Channel::Liveness), _ => None, } } diff --git a/code/crates/network/src/lib.rs b/code/crates/network/src/lib.rs index 14328af24..93a44a199 100644 --- a/code/crates/network/src/lib.rs +++ b/code/crates/network/src/lib.rs @@ -147,7 +147,8 @@ pub enum Event { Listening(Multiaddr), PeerConnected(PeerId), PeerDisconnected(PeerId), - Message(Channel, PeerId, Bytes), + ConsensusMessage(Channel, PeerId, Bytes), + LivenessMessage(Channel, PeerId, Bytes), Sync(sync::RawMessage), } @@ -569,15 +570,26 @@ async fn handle_gossipsub_event( message.data.len() ); - let event = Event::Message( - channel, - PeerId::from_libp2p(&peer_id), - Bytes::from(message.data), - ); - - if let Err(e) = tx_event.send(event).await { - error!("Error sending message to handle: {e}"); - return ControlFlow::Break(()); + if channel == Channel::Liveness { + let event = Event::LivenessMessage( + channel, + PeerId::from_libp2p(&peer_id), + Bytes::from(message.data), + ); + if let Err(e) = tx_event.send(event).await { + error!("Error sending message to handle: {e}"); + return ControlFlow::Break(()); + } + } else { + let event = Event::ConsensusMessage( + channel, + PeerId::from_libp2p(&peer_id), + Bytes::from(message.data), + ); + if let Err(e) = tx_event.send(event).await { + error!("Error sending message to handle: {e}"); + return ControlFlow::Break(()); + } } } @@ -636,7 +648,7 @@ async fn handle_broadcast_event( message.len() ); - let event = Event::Message( + let event = Event::ConsensusMessage( channel, PeerId::from_libp2p(&peer_id), Bytes::copy_from_slice(message.as_ref()), diff --git a/code/crates/starknet/host/src/codec.rs b/code/crates/starknet/host/src/codec.rs index 23c8d8eb3..3110c13be 100644 --- a/code/crates/starknet/host/src/codec.rs +++ b/code/crates/starknet/host/src/codec.rs @@ -2,10 +2,10 @@ use bytes::Bytes; use prost::Message; use malachitebft_codec::Codec; -use malachitebft_core_consensus::{PeerId, ProposedValue, SignedConsensusMsg}; +use malachitebft_core_consensus::{LivenessMsg, PeerId, ProposedValue, SignedConsensusMsg}; use malachitebft_core_types::{ - CommitCertificate, CommitSignature, PolkaCertificate, PolkaSignature, Round, SignedVote, - Validity, + CommitCertificate, CommitSignature, NilOrVal, PolkaCertificate, PolkaSignature, Round, + RoundCertificate, RoundSignature, SignedVote, Validity, VoteType, }; use malachitebft_engine::util::streaming::{StreamContent, StreamId, StreamMessage}; use malachitebft_starknet_p2p_types::{Felt, FeltExt, Signature}; @@ -67,36 +67,6 @@ impl Codec for ProtobufCodec { } } -// impl Codec> for ProtobufCodec { -// type Error = ProtoError; -// -// fn decode(&self, bytes: Bytes) -> Result, Self::Error> { -// decode_extension(proto::Extension::decode(bytes)?) -// } -// -// fn encode(&self, msg: &SignedExtension) -> Result { -// encode_extension(msg).map(|proto| proto.encode_to_bytes()) -// } -// } -// -// pub fn decode_extension(ext: proto::Extension) -> Result, ProtoError> { -// let signature = ext -// .signature -// .ok_or_else(|| ProtoError::missing_field::("signature")) -// .and_then(p2p::Signature::from_proto)?; -// -// Ok(SignedExtension::new(ext.data, signature)) -// } -// -// pub fn encode_extension( -// ext: &SignedExtension, -// ) -> Result { -// Ok(proto::Extension { -// data: ext.message.clone(), -// signature: Some(ext.signature.to_proto()?), -// }) -// } - pub fn decode_proposed_value( proto: proto::sync::ProposedValue, ) -> Result, ProtoError> { @@ -392,6 +362,124 @@ impl Codec> for ProtobufCodec { } } +pub(crate) fn encode_round_certificate( + certificate: &RoundCertificate, +) -> Result { + Ok(proto::RoundCertificate { + fork_id: certificate.height.fork_id, + block_number: certificate.height.block_number, + round: certificate.round.as_u32().expect("round should not be nil"), + signatures: certificate + .round_signatures + .iter() + .map(|sig| -> Result { + let address = sig.address.to_proto()?; + let signature = encode_signature(&sig.signature)?; + let block_hash = match sig.value_id { + NilOrVal::Nil => None, + NilOrVal::Val(value_id) => Some(value_id.to_proto()?), + }; + Ok(proto::RoundSignature { + vote_type: match sig.vote_type { + VoteType::Prevote => proto::VoteType::Prevote as i32, + VoteType::Precommit => proto::VoteType::Precommit as i32, + }, + validator_address: Some(address), + signature: Some(signature), + block_hash, + }) + }) + .collect::, _>>()?, + }) +} + +pub(crate) fn decode_round_certificate( + certificate: proto::RoundCertificate, +) -> Result, ProtoError> { + Ok(RoundCertificate { + height: Height::new(certificate.block_number, certificate.fork_id), + round: Round::new(certificate.round), + round_signatures: certificate + .signatures + .into_iter() + .map(|sig| -> Result, ProtoError> { + let address = sig.validator_address.ok_or_else(|| { + ProtoError::missing_field::("validator_address") + })?; + let signature = sig.signature.ok_or_else(|| { + ProtoError::missing_field::("signature") + })?; + let signature = decode_signature(signature)?; + let address = Address::from_proto(address)?; + let value_id = match sig.block_hash { + None => NilOrVal::Nil, + Some(block_hash) => NilOrVal::Val(BlockHash::from_proto(block_hash)?), + }; + let vote_type = match sig.vote_type { + 0 => VoteType::Prevote, + 1 => VoteType::Precommit, + _ => return Err(ProtoError::Other("Invalid vote type".to_string())), + }; + Ok(RoundSignature::new(vote_type, value_id, address, signature)) + }) + .collect::, _>>()?, + }) +} + +impl Codec> for ProtobufCodec { + type Error = ProtoError; + + fn decode(&self, bytes: Bytes) -> Result, Self::Error> { + let msg = proto::LivenessMessage::decode(bytes.as_ref())?; + match msg.message { + Some(proto::liveness_message::Message::Vote(vote)) => { + Ok(LivenessMsg::Vote(decode_vote(vote)?)) + } + Some(proto::liveness_message::Message::PolkaCertificate(cert)) => Ok( + LivenessMsg::PolkaCertificate(decode_polka_certificate(cert)?), + ), + Some(proto::liveness_message::Message::RoundCertificate(cert)) => Ok( + LivenessMsg::SkipRoundCertificate(decode_round_certificate(cert)?), + ), + None => Err(ProtoError::missing_field::( + "message", + )), + } + } + + fn encode(&self, msg: &LivenessMsg) -> Result { + match msg { + LivenessMsg::Vote(vote) => { + let message = encode_vote(vote)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::Vote(message)), + } + .encode_to_vec(), + )) + } + LivenessMsg::PolkaCertificate(cert) => { + let message = encode_polka_certificate(cert)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::PolkaCertificate(message)), + } + .encode_to_vec(), + )) + } + LivenessMsg::SkipRoundCertificate(cert) => { + let message = encode_round_certificate(cert)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::RoundCertificate(message)), + } + .encode_to_vec(), + )) + } + } + } +} + impl Codec> for ProtobufCodec where T: Protobuf, @@ -514,8 +602,8 @@ impl Codec> for ProtobufCodec { pub(crate) fn encode_polka_certificate( certificate: &PolkaCertificate, -) -> Result { - Ok(proto::sync::PolkaCertificate { +) -> Result { + Ok(proto::PolkaCertificate { fork_id: certificate.height.fork_id, block_number: certificate.height.block_number, block_hash: Some(certificate.value_id.to_proto()?), @@ -523,10 +611,10 @@ pub(crate) fn encode_polka_certificate( signatures: certificate .polka_signatures .iter() - .map(|sig| -> Result { + .map(|sig| -> Result { let address = sig.address.to_proto()?; let signature = encode_signature(&sig.signature)?; - Ok(proto::sync::PolkaSignature { + Ok(proto::PolkaSignature { validator_address: Some(address), signature: Some(signature), }) @@ -536,11 +624,11 @@ pub(crate) fn encode_polka_certificate( } pub(crate) fn decode_polka_certificate( - certificate: proto::sync::PolkaCertificate, + certificate: proto::PolkaCertificate, ) -> Result, ProtoError> { let block_hash = certificate .block_hash - .ok_or_else(|| ProtoError::missing_field::("block_hash"))?; + .ok_or_else(|| ProtoError::missing_field::("block_hash"))?; Ok(PolkaCertificate { height: Height::new(certificate.block_number, certificate.fork_id), @@ -551,10 +639,10 @@ pub(crate) fn decode_polka_certificate( .into_iter() .map(|sig| -> Result, ProtoError> { let address = sig.validator_address.ok_or_else(|| { - ProtoError::missing_field::("validator_address") + ProtoError::missing_field::("validator_address") })?; let signature = sig.signature.ok_or_else(|| { - ProtoError::missing_field::("signature") + ProtoError::missing_field::("signature") })?; let signature = decode_signature(signature)?; let address = Address::from_proto(address)?; @@ -568,7 +656,7 @@ impl Codec> for ProtobufCodec { type Error = ProtoError; fn decode(&self, bytes: Bytes) -> Result, Self::Error> { - decode_polka_certificate(proto::sync::PolkaCertificate::decode(bytes)?) + decode_polka_certificate(proto::PolkaCertificate::decode(bytes)?) } fn encode(&self, msg: &PolkaCertificate) -> Result { diff --git a/code/crates/starknet/p2p-proto/build.rs b/code/crates/starknet/p2p-proto/build.rs index 6dd13f8c7..e85e87f5f 100644 --- a/code/crates/starknet/p2p-proto/build.rs +++ b/code/crates/starknet/p2p-proto/build.rs @@ -4,6 +4,7 @@ fn main() -> Result<(), Box> { "./proto/p2p/proto/common.proto", "./proto/p2p/proto/transaction.proto", "./proto/p2p/proto/consensus/consensus.proto", + "./proto/p2p/proto/consensus/liveness.proto", ]; for proto in protos { diff --git a/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/consensus.proto b/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/consensus.proto index f8a83a82a..1202510db 100644 --- a/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/consensus.proto +++ b/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/consensus.proto @@ -7,6 +7,11 @@ option go_package = "github.com/starknet-io/starknet-p2pspecs/p2p/proto/consensu // WIP - will change +enum VoteType { + Prevote = 0; + Precommit = 1; +}; + // Contains all variants of mempool and an L1Handler variant to cover all transactions that can be // in a new block. message ConsensusTransaction { @@ -21,11 +26,6 @@ message ConsensusTransaction { } message Vote { - enum VoteType { - Prevote = 0; - Precommit = 1; - }; - // We use a type field to distinguish between prevotes and precommits instead of different // messages, to make sure the data, and therefore the signatures, are unambiguous between // Prevote and Precommit. diff --git a/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/liveness.proto b/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/liveness.proto new file mode 100644 index 000000000..414d63dc9 --- /dev/null +++ b/code/crates/starknet/p2p-proto/proto/p2p/proto/consensus/liveness.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +import "p2p/proto/common.proto"; +import "p2p/proto/consensus/consensus.proto"; + +message PolkaSignature { + // TODO - add flag (no vote, nil, value?) + Address validator_address = 1; + ConsensusSignature signature = 2; +} + +message PolkaCertificate { + uint64 fork_id = 1; + uint64 block_number = 2; + uint32 round = 3; + Hash block_hash = 4; + repeated PolkaSignature signatures = 5; +} + +message RoundSignature { + VoteType vote_type = 1; + Address validator_address = 2; + ConsensusSignature signature = 3; + // This is optional since a vote can be NIL. + optional Hash block_hash = 4; +} + +message RoundCertificate { + uint64 fork_id = 1; + uint64 block_number = 2; + uint32 round = 3; + repeated RoundSignature signatures = 4; +} + +message LivenessMessage { + oneof message { + Vote vote = 1; + PolkaCertificate polka_certificate = 2; + RoundCertificate round_certificate = 3; + } +} diff --git a/code/crates/starknet/p2p-proto/proto/sync.proto b/code/crates/starknet/p2p-proto/proto/sync.proto index aeaa74439..122dd7d22 100644 --- a/code/crates/starknet/p2p-proto/proto/sync.proto +++ b/code/crates/starknet/p2p-proto/proto/sync.proto @@ -4,6 +4,7 @@ package sync; import "p2p/proto/common.proto"; import "p2p/proto/consensus/consensus.proto"; +import "p2p/proto/consensus/liveness.proto"; message Status { PeerID peer_id = 1; @@ -54,20 +55,6 @@ message CommitCertificate { repeated CommitSignature signatures = 5; } -message PolkaSignature { - // TODO - add flag (no vote, nil, value?) - Address validator_address = 1; - ConsensusSignature signature = 2; -} - -message PolkaCertificate { - uint64 fork_id = 1; - uint64 block_number = 2; - uint32 round = 3; - Hash block_hash = 4; - repeated PolkaSignature signatures = 5; -} - message ProposedValue { uint64 fork_id = 1; uint64 block_number = 2; diff --git a/code/crates/starknet/p2p-types/src/vote.rs b/code/crates/starknet/p2p-types/src/vote.rs index e1d54a923..10e782de9 100644 --- a/code/crates/starknet/p2p-types/src/vote.rs +++ b/code/crates/starknet/p2p-types/src/vote.rs @@ -90,18 +90,16 @@ impl proto::Protobuf for Vote { } } -fn common_to_proto_vote_type( - vote_type: VoteType, -) -> malachitebft_starknet_p2p_proto::vote::VoteType { +fn common_to_proto_vote_type(vote_type: VoteType) -> malachitebft_starknet_p2p_proto::VoteType { match vote_type { - VoteType::Prevote => p2p_proto::vote::VoteType::Prevote, - VoteType::Precommit => p2p_proto::vote::VoteType::Precommit, + VoteType::Prevote => p2p_proto::VoteType::Prevote, + VoteType::Precommit => p2p_proto::VoteType::Precommit, } } -fn proto_to_common_vote_type(vote_type: p2p_proto::vote::VoteType) -> VoteType { +fn proto_to_common_vote_type(vote_type: p2p_proto::VoteType) -> VoteType { match vote_type { - p2p_proto::vote::VoteType::Prevote => VoteType::Prevote, - p2p_proto::vote::VoteType::Precommit => VoteType::Precommit, + p2p_proto::VoteType::Prevote => VoteType::Prevote, + p2p_proto::VoteType::Precommit => VoteType::Precommit, } } diff --git a/code/crates/starknet/test/src/tests/vote_sync_bcast.rs b/code/crates/starknet/test/src/tests/vote_sync_bcast.rs index 1f8b389cf..449970610 100644 --- a/code/crates/starknet/test/src/tests/vote_sync_bcast.rs +++ b/code/crates/starknet/test/src/tests/vote_sync_bcast.rs @@ -1,6 +1,7 @@ use std::time::Duration; use malachitebft_config::VoteSyncMode; +use malachitebft_core_types::VoteType; use crate::{TestBuilder, TestParams}; @@ -14,14 +15,14 @@ pub async fn crash_restart_from_start() { test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); @@ -64,14 +65,14 @@ pub async fn crash_restart_from_latest() { test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); @@ -105,14 +106,14 @@ pub async fn start_late() { test.add_node() .start() .wait_until(1) - .expect_vote_rebroadcast(1) + .expect_vote_rebroadcast(1, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(1) - .expect_vote_rebroadcast(1) + .expect_vote_rebroadcast(1, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); diff --git a/code/crates/test/app/config.toml b/code/crates/test/app/config.toml index bc6c70c4a..2a44109c9 100644 --- a/code/crates/test/app/config.toml +++ b/code/crates/test/app/config.toml @@ -62,6 +62,10 @@ timeout_commit = "0s" # Override with MALACHITE__CONSENSUS__TIMEOUT_STEP env variable timeout_step = "2s" +# How long we wait after entering a round before starting the rebroadcast liveness protocol +# Override with MALACHITE__CONSENSUS__TIMEOUT_REBROADCAST env variable +timeout_rebroadcast = "5s" + # The message(s) required to carry the value payload. # Available options are: # - "parts-only": Full value is included in the proposal parts and there is no explicit Proposal message (default) diff --git a/code/crates/test/app/src/app.rs b/code/crates/test/app/src/app.rs index 026828bc9..043370fd7 100644 --- a/code/crates/test/app/src/app.rs +++ b/code/crates/test/app/src/app.rs @@ -318,12 +318,16 @@ pub async fn run( } => { info!(%height, %valid_round, "Restreaming existing proposal..."); - assert_ne!(valid_round, Round::Nil, "valid_round should not be nil"); - + // Look for a proposal at valid_round or round(should be already stored) + let proposal_round = if valid_round == Round::Nil { + round + } else { + valid_round + }; // Look for a proposal for the given value_id at valid_round (should be already stored) let proposal = state .store - .get_undecided_proposal(height, valid_round, value_id) + .get_undecided_proposal(height, proposal_round, value_id) .await?; if let Some(proposal) = proposal { diff --git a/code/crates/test/build.rs b/code/crates/test/build.rs index ab2ba15bf..28291ad21 100644 --- a/code/crates/test/build.rs +++ b/code/crates/test/build.rs @@ -1,7 +1,11 @@ use std::io::Result; fn main() -> Result<()> { - let protos = &["proto/consensus.proto", "proto/sync.proto"]; + let protos = &[ + "proto/consensus.proto", + "proto/sync.proto", + "proto/liveness.proto", + ]; for proto in protos { println!("cargo:rerun-if-changed={proto}"); diff --git a/code/crates/test/framework/src/lib.rs b/code/crates/test/framework/src/lib.rs index 8b62bfac5..9ae34e0f0 100644 --- a/code/crates/test/framework/src/lib.rs +++ b/code/crates/test/framework/src/lib.rs @@ -350,6 +350,8 @@ where break 'inner; } Err(e) => { + error!("Event handler returned an error: {e}"); + event_monitor.abort(); handle.kill(Some("Test failed".to_string())).await.unwrap(); diff --git a/code/crates/test/framework/src/node.rs b/code/crates/test/framework/src/node.rs index 047c316c4..0eab4deb8 100644 --- a/code/crates/test/framework/src/node.rs +++ b/code/crates/test/framework/src/node.rs @@ -5,7 +5,7 @@ use eyre::bail; use tracing::info; use malachitebft_core_consensus::{LocallyProposedValue, SignedConsensusMsg}; -use malachitebft_core_types::{Context, Height, SignedVote, Vote, VotingPower}; +use malachitebft_core_types::{Context, Height, SignedVote, Vote, VoteType, VotingPower}; use malachitebft_engine::util::events::Event; use malachitebft_test::middleware::{DefaultMiddleware, Middleware}; @@ -176,9 +176,14 @@ where }) } - pub fn expect_vote_rebroadcast(&mut self, at_height: u64) -> &mut Self { + pub fn expect_vote_rebroadcast( + &mut self, + at_height: u64, + at_round: u32, + vote_type: VoteType, + ) -> &mut Self { self.on_event(move |event, _| { - let Event::Rebroadcast(msg) = event else { + let Event::RebroadcastVote(msg) = event else { return Ok(HandlerResult::WaitForNextEvent); }; @@ -188,7 +193,88 @@ where bail!("Unexpected vote rebroadcast for height {height}, expected {at_height}") } - info!(%height, %round, "Rebroadcasted vote"); + if round.as_u32() != Some(at_round) { + bail!("Unexpected vote rebroadcast for round {round}, expected {at_round}") + } + + if vote_type != msg.vote_type() { + bail!( + "Unexpected vote type {vote_type:?}, expected {:?}", + msg.vote_type() + ) + } + + info!(%height, %round, ?vote_type, "Rebroadcasted vote"); + + Ok(HandlerResult::ContinueTest) + }) + } + + pub fn expect_round_certificate_rebroadcast( + &mut self, + at_height: u64, + at_round: u32, + ) -> &mut Self { + self.on_event(move |event, _| { + let Event::RebroadcastRoundCertificate(msg) = event else { + return Ok(HandlerResult::WaitForNextEvent); + }; + + let (height, round) = (msg.height, msg.round); + + if height.as_u64() != at_height { + bail!("Unexpected round certificate rebroadcast for height {height}, expected {at_height}") + } + + if round.as_u32() != Some(at_round) { + bail!("Unexpected round certificate rebroadcast for round {round}, expected {at_round}") + } + + info!(%height, %round, "Rebroadcasted round certificate"); + + Ok(HandlerResult::ContinueTest) + }) + } + + pub fn expect_skip_round_certificate(&mut self, at_height: u64, at_round: u32) -> &mut Self { + self.on_event(move |event, _| { + let Event::SkipRoundCertificate(msg) = event else { + return Ok(HandlerResult::WaitForNextEvent); + }; + + let (height, round) = (msg.height, msg.round); + + if height.as_u64() != at_height { + bail!("Unexpected round certificate broadcast for height {height}, expected {at_height}") + } + + if round.as_u32() != Some(at_round) { + bail!("Unexpected round certificate broadcast for round {round}, expected {at_round}") + } + + info!(%height, %round, "Broadcasted skip round certificate"); + + Ok(HandlerResult::ContinueTest) + }) + } + + pub fn expect_polka_certificate(&mut self, at_height: u64, at_round: u32) -> &mut Self { + self.on_event(move |event, _| { + let Event::PolkaCertificate(msg) = event else { + return Ok(HandlerResult::WaitForNextEvent); + }; + + let (height, round) = (msg.height, msg.round); + + if height.as_u64() != at_height { + bail!("Unexpected round certificate rebroadcast for height {height}, expected {at_height}") + } + + if round.as_u32() != Some(at_round) { + bail!("Unexpected round certificate rebroadcast for round {round}, expected {at_round}") + } + + info!(%height, %round, "Broadcasted round certificate"); Ok(HandlerResult::ContinueTest) }) @@ -244,4 +330,9 @@ where pub fn is_full_node(&self) -> bool { self.voting_power == 0 } + + pub fn with(&mut self, f: impl FnOnce(&mut Self)) -> &mut Self { + f(self); + self + } } diff --git a/code/crates/test/proto/liveness.proto b/code/crates/test/proto/liveness.proto new file mode 100644 index 000000000..0d05ccc67 --- /dev/null +++ b/code/crates/test/proto/liveness.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package test; + +import "consensus.proto"; + +message PolkaSignature { + // TODO: Add flag (no vote, nil, value?) + Address validator_address = 1; + Signature signature = 2; +} + +message PolkaCertificate { + uint64 height = 1; + uint32 round = 2; + ValueId value_id = 3; + repeated PolkaSignature signatures = 4; +} + +message RoundSignature { + VoteType vote_type = 1; + Address validator_address = 2; + Signature signature = 3; + // This is optional since a vote can be NIL. + optional ValueId value_id = 4; +} + +message RoundCertificate { + uint64 height = 1; + uint32 round = 2; + repeated RoundSignature signatures = 3; +} + +message LivenessMessage { + oneof message { + SignedMessage vote = 1; + PolkaCertificate polka_certificate = 2; + RoundCertificate round_certificate = 3; + } +} diff --git a/code/crates/test/proto/sync.proto b/code/crates/test/proto/sync.proto index 027d0dc27..0e3a4bdf1 100644 --- a/code/crates/test/proto/sync.proto +++ b/code/crates/test/proto/sync.proto @@ -1,6 +1,7 @@ syntax = "proto3"; import "consensus.proto"; +import "liveness.proto"; package test; @@ -41,19 +42,6 @@ message CommitCertificate { repeated CommitSignature signatures = 4; } -message PolkaSignature { - // TODO: Add flag (no vote, nil, value?) - Address validator_address = 1; - Signature signature = 2; -} - -message PolkaCertificate { - uint64 height = 1; - uint32 round = 2; - ValueId value_id = 3; - repeated PolkaSignature signatures = 4; -} - message ProposedValue { uint64 height = 1; uint32 round = 2; diff --git a/code/crates/test/src/codec/proto/mod.rs b/code/crates/test/src/codec/proto/mod.rs index 81b8a54b7..2e7331b87 100644 --- a/code/crates/test/src/codec/proto/mod.rs +++ b/code/crates/test/src/codec/proto/mod.rs @@ -3,16 +3,17 @@ use prost::Message; use malachitebft_app::engine::util::streaming::{StreamContent, StreamId, StreamMessage}; use malachitebft_codec::Codec; -use malachitebft_core_consensus::{ProposedValue, SignedConsensusMsg}; +use malachitebft_core_consensus::{LivenessMsg, ProposedValue, SignedConsensusMsg}; use malachitebft_core_types::{ - CommitCertificate, CommitSignature, PolkaCertificate, PolkaSignature, Round, SignedExtension, - SignedProposal, SignedVote, Validity, VoteSet, + CommitCertificate, CommitSignature, NilOrVal, PolkaCertificate, PolkaSignature, Round, + RoundCertificate, RoundSignature, SignedExtension, SignedProposal, SignedVote, Validity, + VoteSet, }; use malachitebft_proto::{Error as ProtoError, Protobuf}; use malachitebft_signing_ed25519::Signature; use malachitebft_sync::{self as sync, PeerId}; -use crate::proto; +use crate::{decode_votetype, encode_votetype, proto}; use crate::{Address, Height, Proposal, ProposalPart, TestContext, Value, ValueId, Vote}; #[derive(Copy, Clone, Debug)] @@ -113,6 +114,117 @@ impl Codec> for ProtobufCodec { } } +pub fn encode_round_certificate( + certificate: &RoundCertificate, +) -> Result { + Ok(proto::RoundCertificate { + height: certificate.height.as_u64(), + round: certificate.round.as_u32().expect("round should not be nil"), + signatures: certificate + .round_signatures + .iter() + .map(|sig| -> Result { + let value_id = match sig.value_id { + NilOrVal::Nil => None, + NilOrVal::Val(value_id) => Some(value_id.to_proto()?), + }; + Ok(proto::RoundSignature { + vote_type: encode_votetype(sig.vote_type).into(), + validator_address: Some(sig.address.to_proto()?), + signature: Some(encode_signature(&sig.signature)), + value_id, + }) + }) + .collect::, _>>()?, + }) +} + +pub fn decode_round_certificate( + certificate: proto::RoundCertificate, +) -> Result, ProtoError> { + Ok(RoundCertificate { + height: Height::new(certificate.height), + round: Round::new(certificate.round), + round_signatures: certificate + .signatures + .into_iter() + .map(|sig| -> Result, ProtoError> { + let vote_type = decode_votetype(sig.vote_type()); + let address = sig.validator_address.ok_or_else(|| { + ProtoError::missing_field::("validator_address") + })?; + + let signature = sig.signature.ok_or_else(|| { + ProtoError::missing_field::("signature") + })?; + + let value_id = match sig.value_id { + None => NilOrVal::Nil, + Some(value_id) => NilOrVal::Val(ValueId::from_proto(value_id)?), + }; + + let signature = decode_signature(signature)?; + let address = Address::from_proto(address)?; + Ok(RoundSignature::new(vote_type, value_id, address, signature)) + }) + .collect::, _>>()?, + }) +} + +impl Codec> for ProtobufCodec { + type Error = ProtoError; + + fn decode(&self, bytes: Bytes) -> Result, Self::Error> { + let msg = proto::LivenessMessage::decode(bytes.as_ref())?; + match msg.message { + Some(proto::liveness_message::Message::Vote(vote)) => { + Ok(LivenessMsg::Vote(decode_vote(vote)?)) + } + Some(proto::liveness_message::Message::PolkaCertificate(cert)) => Ok( + LivenessMsg::PolkaCertificate(decode_polka_certificate(cert)?), + ), + Some(proto::liveness_message::Message::RoundCertificate(cert)) => Ok( + LivenessMsg::SkipRoundCertificate(decode_round_certificate(cert)?), + ), + None => Err(ProtoError::missing_field::( + "message", + )), + } + } + + fn encode(&self, msg: &LivenessMsg) -> Result { + match msg { + LivenessMsg::Vote(vote) => { + let message = encode_vote(vote)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::Vote(message)), + } + .encode_to_vec(), + )) + } + LivenessMsg::PolkaCertificate(cert) => { + let message = encode_polka_certificate(cert)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::PolkaCertificate(message)), + } + .encode_to_vec(), + )) + } + LivenessMsg::SkipRoundCertificate(cert) => { + let message = encode_round_certificate(cert)?; + Ok(Bytes::from( + proto::LivenessMessage { + message: Some(proto::liveness_message::Message::RoundCertificate(message)), + } + .encode_to_vec(), + )) + } + } + } +} + impl Codec> for ProtobufCodec { type Error = ProtoError; @@ -557,3 +669,55 @@ pub fn decode_signature(signature: proto::Signature) -> Result proto::VoteType { +pub fn encode_votetype(vote_type: VoteType) -> proto::VoteType { match vote_type { VoteType::Prevote => proto::VoteType::Prevote, VoteType::Precommit => proto::VoteType::Precommit, @@ -152,7 +152,7 @@ fn encode_votetype(vote_type: VoteType) -> proto::VoteType { } #[cfg_attr(coverage_nightly, coverage(off))] -fn decode_votetype(vote_type: proto::VoteType) -> VoteType { +pub fn decode_votetype(vote_type: proto::VoteType) -> VoteType { match vote_type { proto::VoteType::Prevote => VoteType::Prevote, proto::VoteType::Precommit => VoteType::Precommit, diff --git a/code/crates/test/tests/it/liveness.rs b/code/crates/test/tests/it/liveness.rs new file mode 100644 index 000000000..f4abcf2b4 --- /dev/null +++ b/code/crates/test/tests/it/liveness.rs @@ -0,0 +1,104 @@ +use std::time::Duration; + +use informalsystems_malachitebft_test::TestContext; +use malachitebft_config::VoteSyncMode; +use malachitebft_core_consensus::HIDDEN_LOCK_ROUND; +use malachitebft_core_types::Round; +use malachitebft_test_framework::TestNode; + +use crate::middlewares::PrevoteNil; +use crate::{TestBuilder, TestParams}; + +fn expect_round_certificate_rebroadcasts(node: &mut TestNode) { + node.expect_skip_round_certificate(1, 0) + .expect_skip_round_certificate(1, 1) + .expect_skip_round_certificate(1, 2); +} + +#[tokio::test] +async fn round_certificate_rebroadcast() { + const FINAL_HEIGHT: u64 = 3; + + let mut test = TestBuilder::<()>::new(); + + test.add_node() + .with_middleware(PrevoteNil::when(|height, round, _| { + height.as_u64() == 1 && round.as_i64() <= 2 + })) + .start() + .wait_until(1) + .with(expect_round_certificate_rebroadcasts) + .wait_until(FINAL_HEIGHT) + .success(); + + test.add_node() + .start() + .wait_until(1) + .with(expect_round_certificate_rebroadcasts) + .wait_until(FINAL_HEIGHT) + .success(); + + test.add_node() + .start() + .wait_until(1) + .with(expect_round_certificate_rebroadcasts) + .wait_until(FINAL_HEIGHT) + .success(); + + test.build() + .run_with_params( + Duration::from_secs(30), + TestParams { + enable_value_sync: false, + vote_sync_mode: Some(VoteSyncMode::Rebroadcast), + ..Default::default() + }, + ) + .await +} + +fn expect_hidden_lock_messages(node: &mut TestNode, round: Round) { + node.expect_polka_certificate(1, round.as_u32().expect("non-nil round")); +} + +#[tokio::test] +async fn polka_certificate_for_hidden_lock() { + const FINAL_HEIGHT: u64 = 3; + + let mut test = TestBuilder::<()>::new(); + + test.add_node() + .with_middleware(PrevoteNil::when(|height, round, _| { + height.as_u64() == 1 && round < HIDDEN_LOCK_ROUND + })) + .start() + .wait_until(1) + .with(|node| expect_hidden_lock_messages(node, HIDDEN_LOCK_ROUND)) + .wait_until(FINAL_HEIGHT) + .success(); + + test.add_node() + .start() + .wait_until(1) + .with(|node| expect_hidden_lock_messages(node, HIDDEN_LOCK_ROUND)) + .wait_until(FINAL_HEIGHT) + .success(); + + test.add_node() + .start() + .wait_until(1) + .with(|node| expect_hidden_lock_messages(node, HIDDEN_LOCK_ROUND)) + .wait_until(FINAL_HEIGHT) + .success(); + + test.build() + .run_with_params( + Duration::from_secs(120), + TestParams { + enable_value_sync: false, + vote_sync_mode: Some(VoteSyncMode::Rebroadcast), + ..Default::default() + }, + ) + .await +} diff --git a/code/crates/test/tests/it/main.rs b/code/crates/test/tests/it/main.rs index 7f68d0813..674046da9 100644 --- a/code/crates/test/tests/it/main.rs +++ b/code/crates/test/tests/it/main.rs @@ -1,4 +1,6 @@ mod full_nodes; +mod liveness; +mod middlewares; mod n3f0; mod n3f0_consensus_mode; mod n3f0_pubsub_protocol; diff --git a/code/crates/test/tests/it/middlewares.rs b/code/crates/test/tests/it/middlewares.rs new file mode 100644 index 000000000..e06025aac --- /dev/null +++ b/code/crates/test/tests/it/middlewares.rs @@ -0,0 +1,75 @@ +use informalsystems_malachitebft_test::{self as malachitebft_test}; + +use malachitebft_core_consensus::LocallyProposedValue; +use malachitebft_core_types::{NilOrVal, Round}; +use malachitebft_test::middleware::Middleware; +use malachitebft_test::{Address, Height, TestContext, ValueId, Vote}; + +#[derive(Copy, Clone, Debug)] +pub struct ByzantineProposer; + +impl Middleware for ByzantineProposer { + fn on_propose_value( + &self, + _ctx: &TestContext, + proposal: &mut LocallyProposedValue, + reproposal: bool, + ) { + use informalsystems_malachitebft_test::Value; + use rand::Rng; + + if !reproposal { + tracing::warn!( + "ByzantineProposer: First time proposing value {:}", + proposal.value.id() + ); + + // Do not change the value if it is the first time we propose it + return; + } + + // Make up a new value that is different from the one we are supposed to propose + let new_value = loop { + let new_value = Value::new(rand::thread_rng().gen_range(100..=100000)); + if new_value != proposal.value { + break new_value; + } + }; + + tracing::warn!( + "ByzantineProposer: Not re-using previously built value {:} but a new one {:}", + proposal.value.id(), + new_value.id() + ); + + proposal.value = new_value; + } +} + +#[derive(Copy, Clone, Debug)] +pub struct PrevoteNil { + enabled: fn(Height, Round, &NilOrVal) -> bool, +} + +impl PrevoteNil { + pub fn when(enabled: fn(Height, Round, &NilOrVal) -> bool) -> Self { + Self { enabled } + } +} + +impl Middleware for PrevoteNil { + fn new_prevote( + &self, + _ctx: &TestContext, + height: Height, + round: Round, + value_id: NilOrVal, + address: Address, + ) -> Vote { + if (self.enabled)(height, round, &value_id) { + Vote::new_prevote(height, round, NilOrVal::Nil, address) + } else { + Vote::new_prevote(height, round, value_id, address) + } + } +} diff --git a/code/crates/test/tests/it/vote_sync_bcast.rs b/code/crates/test/tests/it/vote_sync_bcast.rs index 5539c9e82..98645a811 100644 --- a/code/crates/test/tests/it/vote_sync_bcast.rs +++ b/code/crates/test/tests/it/vote_sync_bcast.rs @@ -1,6 +1,7 @@ use std::time::Duration; use malachitebft_config::{ValuePayload, VoteSyncMode}; +use malachitebft_core_types::VoteType; use crate::{TestBuilder, TestParams}; @@ -17,14 +18,14 @@ pub async fn crash_restart_from_start() { test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); @@ -68,14 +69,14 @@ pub async fn crash_restart_from_latest() { test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(CRASH_HEIGHT) - .expect_vote_rebroadcast(CRASH_HEIGHT) + .expect_vote_rebroadcast(CRASH_HEIGHT, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); @@ -109,14 +110,14 @@ pub async fn start_late() { test.add_node() .start() .wait_until(1) - .expect_vote_rebroadcast(1) + .expect_vote_rebroadcast(1, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); test.add_node() .start() .wait_until(1) - .expect_vote_rebroadcast(1) + .expect_vote_rebroadcast(1, 0, VoteType::Prevote) .wait_until(HEIGHT) .success(); diff --git a/code/crates/test/tests/it/wal.rs b/code/crates/test/tests/it/wal.rs index 24f5af267..6c8131442 100644 --- a/code/crates/test/tests/it/wal.rs +++ b/code/crates/test/tests/it/wal.rs @@ -7,11 +7,11 @@ use informalsystems_malachitebft_test::{self as malachitebft_test}; use malachitebft_config::{ValuePayload, VoteSyncMode}; use malachitebft_core_consensus::LocallyProposedValue; -use malachitebft_core_types::{NilOrVal, Round, SignedVote}; +use malachitebft_core_types::SignedVote; use malachitebft_engine::util::events::Event; -use malachitebft_test::middleware::Middleware; -use malachitebft_test::{Address, Height, TestContext, ValueId, Vote}; +use malachitebft_test::TestContext; +use crate::middlewares::{ByzantineProposer, PrevoteNil}; use crate::{HandlerResult, TestBuilder, TestParams}; #[tokio::test] @@ -464,7 +464,7 @@ async fn wal_multi_rounds(params: TestParams) { let mut test = TestBuilder::<()>::new(); test.add_node() - .with_middleware(PrevoteNil) + .with_middleware(PrevoteNil::when(|_, round, _| round.as_i64() <= 3)) .start() .wait_until(CRASH_HEIGHT) .wait_until_round(3) @@ -494,64 +494,3 @@ async fn wal_multi_rounds(params: TestParams) { ) .await } - -#[derive(Copy, Clone, Debug)] -struct ByzantineProposer; - -impl Middleware for ByzantineProposer { - fn on_propose_value( - &self, - _ctx: &TestContext, - proposal: &mut LocallyProposedValue, - reproposal: bool, - ) { - use informalsystems_malachitebft_test::Value; - use rand::Rng; - - if !reproposal { - tracing::warn!( - "ByzantineProposer: First time proposing value {:}", - proposal.value.id() - ); - - // Do not change the value if it is the first time we propose it - return; - } - - // Make up a new value that is different from the one we are supposed to propose - let new_value = loop { - let new_value = Value::new(rand::thread_rng().gen_range(100..=100000)); - if new_value != proposal.value { - break new_value; - } - }; - - tracing::warn!( - "ByzantineProposer: Not re-using previously built value {:} but a new one {:}", - proposal.value.id(), - new_value.id() - ); - - proposal.value = new_value; - } -} - -#[derive(Copy, Clone, Debug)] -struct PrevoteNil; - -impl Middleware for PrevoteNil { - fn new_prevote( - &self, - _ctx: &TestContext, - height: Height, - round: Round, - value_id: NilOrVal, - address: Address, - ) -> Vote { - if round.as_i64() <= 3 { - Vote::new_prevote(height, round, NilOrVal::Nil, address) - } else { - Vote::new_prevote(height, round, value_id, address) - } - } -} diff --git a/code/examples/channel/config.toml b/code/examples/channel/config.toml index 9e7c472e1..50912f687 100644 --- a/code/examples/channel/config.toml +++ b/code/examples/channel/config.toml @@ -62,6 +62,10 @@ timeout_commit = "0s" # Override with MALACHITE__CONSENSUS__TIMEOUT_STEP env variable timeout_step = "2s" +# How long we wait after entering a round before starting the rebroadcast liveness protocol +# Override with MALACHITE__CONSENSUS__TIMEOUT_REBROADCAST env variable +timeout_rebroadcast = "5s" + # The message(s) required to carry the value payload. # Available options are: # - "parts-only": Full value is included in the proposal parts and there is no explicit Proposal message (default) diff --git a/code/examples/channel/src/app.rs b/code/examples/channel/src/app.rs index da8e735a6..b5d66e40d 100644 --- a/code/examples/channel/src/app.rs +++ b/code/examples/channel/src/app.rs @@ -296,12 +296,17 @@ pub async fn run(state: &mut State, channels: &mut Channels) -> eyr address: _, value_id, } => { - // Look for a proposal at valid_round (should be already stored) - info!(%height, %valid_round, "Restreaming existing propos*al..."); + // Look for a proposal at valid_round or round(should be already stored) + let proposal_round = if valid_round == Round::Nil { + round + } else { + valid_round + }; + info!(%height, %proposal_round, "Restreaming existing propos*al..."); let proposal = state .store - .get_undecided_proposal(height, valid_round, value_id) + .get_undecided_proposal(height, proposal_round, value_id) .await?; if let Some(proposal) = proposal { diff --git a/code/examples/channel/src/state.rs b/code/examples/channel/src/state.rs index 964a2f162..851c8ec4d 100644 --- a/code/examples/channel/src/state.rs +++ b/code/examples/channel/src/state.rs @@ -408,7 +408,7 @@ impl State { /// validators from the genesis validator set. pub fn get_validator_set(&self, height: Height) -> ValidatorSet { let num_validators = self.genesis.validator_set.len(); - let selection_size = (num_validators + 1) / 2; + let selection_size = num_validators.div_ceil(2); if num_validators <= selection_size { return self.genesis.validator_set.clone(); diff --git a/code/scripts/spawn.bash b/code/scripts/spawn.bash index d3204d91d..95230ca8f 100755 --- a/code/scripts/spawn.bash +++ b/code/scripts/spawn.bash @@ -38,18 +38,19 @@ fi # Environment variables export MALACHITE__CONSENSUS__P2P__PROTOCOL__TYPE="gossipsub" -export MALACHITE__CONSENSUS__TIMEOUT_PROPOSE="2s" -export MALACHITE__CONSENSUS__TIMEOUT_PROPOSE_DELTA="1s" +export MALACHITE__CONSENSUS__TIMEOUT_PROPOSE="3s" +export MALACHITE__CONSENSUS__TIMEOUT_PROPOSE_DELTA="500ms" export MALACHITE__CONSENSUS__TIMEOUT_PREVOTE="1s" +export MALACHITE__CONSENSUS__TIMEOUT_PREVOTE_DELTA="500ms" export MALACHITE__CONSENSUS__TIMEOUT_PRECOMMIT="1s" -export MALACHITE__CONSENSUS__TIMEOUT_COMMIT="0s" +export MALACHITE__CONSENSUS__TIMEOUT_PRECOMMIT_DELTA="500ms" # Set the timeout step to 2 seconds to trigger the vote sync and polka certificate faster export MALACHITE__CONSENSUS__TIMEOUT_STEP="2s" # Set to request-response to be able to sync polka certificates, "broadcast" does not yet send the certificates -export MALACHITE__CONSENSUS__VOTE_SYNC__MODE="request-response" +export MALACHITE__CONSENSUS__VOTE_SYNC__MODE="rebroadcast" export MALACHITE__MEMPOOL__MAX_TX_COUNT="10000" export MALACHITE__MEMPOOL__GOSSIP_BATCH_SIZE=0 -export MALACHITE__TEST__MAX_BLOCK_SIZE="50KiB" +export MALACHITE__TEST__MAX_BLOCK_SIZE="1024KiB" # Only use "parts-only" with starknet app for now, but for the channel app use "proposal-and-parts". # "proposal-and-parts" also works for starknet app export MALACHITE__TEST__VALUE_PAYLOAD="proposal-and-parts"