diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f9478d1369..a8919337a9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -beacon_node/network/ @jxs -beacon_node/lighthouse_network/ @jxs +/beacon_node/network/ @jxs +/beacon_node/lighthouse_network/ @jxs diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 0f91c86617..817fd9524d 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -19,11 +19,11 @@ env: # Disable debug info (see https://github.com/sigp/lighthouse/issues/4005) RUSTFLAGS: "-D warnings -C debuginfo=0" # Prevent Github API rate limiting. - LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # NOTE: this token is a personal access token on Jimmy's account due to the default GITHUB_TOKEN + # not having access to other repositories. We should eventually devise a better solution here. + LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.LIGHTHOUSE_GITHUB_TOKEN }} # Enable self-hosted runners for the sigp repo only. SELF_HOSTED_RUNNERS: ${{ github.repository == 'sigp/lighthouse' }} - # Self-hosted runners need to reference a different host for `./watch` tests. - WATCH_HOST: ${{ github.repository == 'sigp/lighthouse' && 'host.docker.internal' || 'localhost' }} # Disable incremental compilation CARGO_INCREMENTAL: 0 # Enable portable to prevent issues with caching `blst` for the wrong CPU type diff --git a/Cargo.lock b/Cargo.lock index cbce0359ba..b3b4069e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ "rpassword", "serde", "serde_yaml", - "slog", + "tracing", "types", "validator_dir", "zeroize", @@ -72,12 +72,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aead" version = "0.5.2" @@ -204,9 +198,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc1360603efdfba91151e623f13a4f4d3dc4af4adc1cbd90bf37c81e84db4c77" +checksum = "8c66bb6715b7499ea755bde4c96223ae8eb74e05c014ab38b9db602879ffb825" dependencies = [ "alloy-rlp", "arbitrary", @@ -214,11 +208,11 @@ dependencies = [ "cfg-if", "const-hex", "derive_arbitrary", - "derive_more 1.0.0", + "derive_more 2.0.1", "foldhash", "getrandom 0.2.15", "hashbrown 0.15.2", - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "k256 0.13.4", "keccak-asm", @@ -227,7 +221,7 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "ruint", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "sha3 0.10.8", "tiny-keccak", @@ -252,7 +246,7 @@ checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -328,9 +322,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -522,7 +516,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -534,7 +528,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -564,6 +558,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-io" version = "2.4.0" @@ -602,18 +608,18 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -676,7 +682,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -685,61 +691,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -793,9 +744,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" [[package]] name = "beacon_chain" @@ -838,10 +789,6 @@ dependencies = [ "serde", "serde_json", "slasher", - "slog", - "slog-async", - "slog-term", - "sloggers", "slot_clock", "smallvec", "ssz_types", @@ -853,6 +800,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "tree_hash", "tree_hash_derive", "types", @@ -860,7 +808,7 @@ dependencies = [ [[package]] name = "beacon_node" -version = "6.0.1" +version = "7.0.0-beta.5" dependencies = [ "account_utils", "beacon_chain", @@ -882,10 +830,10 @@ dependencies = [ "sensitive_url", "serde_json", "slasher", - "slog", "store", "strum", "task_executor", + "tracing", "types", "unused_port", ] @@ -901,10 +849,10 @@ dependencies = [ "itertools 0.10.5", "logging", "serde", - "slog", "slot_clock", "strum", "tokio", + "tracing", "types", "validator_metrics", "validator_test_rig", @@ -923,12 +871,12 @@ dependencies = [ "num_cpus", "parking_lot 0.12.3", "serde", - "slog", "slot_clock", "strum", "task_executor", "tokio", "tokio-util", + "tracing", "types", ] @@ -947,7 +895,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -960,24 +908,24 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.100", "which", ] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -987,9 +935,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" @@ -1070,9 +1018,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" dependencies = [ "cc", "glob", @@ -1088,7 +1036,7 @@ checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" dependencies = [ "blst", "byte-slice-cast", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "pairing", "rand_core 0.6.4", @@ -1096,19 +1044,9 @@ dependencies = [ "subtle", ] -[[package]] -name = "bollard-stubs" -version = "1.42.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" -dependencies = [ - "serde", - "serde_with", -] - [[package]] name = "boot_node" -version = "6.0.1" +version = "7.0.0-beta.5" dependencies = [ "beacon_node", "bytes", @@ -1121,11 +1059,9 @@ dependencies = [ "log", "logging", "serde", - "slog", - "slog-async", - "slog-scope", - "slog-term", "tokio", + "tracing", + "tracing-subscriber", "types", ] @@ -1165,9 +1101,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "byteorder" @@ -1177,9 +1113,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -1196,12 +1132,11 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -1246,7 +1181,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -1260,9 +1195,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.11" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -1316,14 +1251,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", - "windows-targets 0.52.6", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -1386,9 +1323,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -1396,9 +1333,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -1409,14 +1346,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1461,6 +1398,7 @@ dependencies = [ "http_metrics", "kzg", "lighthouse_network", + "logging", "metrics", "monitoring_api", "network", @@ -1471,7 +1409,6 @@ dependencies = [ "serde_yaml", "slasher", "slasher_service", - "slog", "slot_clock", "state_processing", "store", @@ -1479,14 +1416,16 @@ dependencies = [ "time", "timer", "tokio", + "tracing", + "tracing-subscriber", "types", ] [[package]] name = "cmake" -version = "0.1.53" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -1499,11 +1438,10 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "lazy_static", "windows-sys 0.59.0", ] @@ -1551,6 +1489,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1599,13 +1557,13 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_bls12_381" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48603155907d588e487aea229f61a28d9a918c95c9aa987055ba29502225810b" +checksum = "76f9cdad245e39a3659bc4c8958e93de34bd31ba3131ead14ccfb4b2cd60e52d" dependencies = [ "blst", "blstrs", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "pairing", "subtle", @@ -1613,9 +1571,9 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_erasure_codes" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf616e4b4f1799191bb1e70b8a29f65e95ab5d74c59972a34998de488d01efd" +checksum = "581d28bcc93eecd97a04cebc5293271e0f41650f03c102f24d6cd784cbedb9f2" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_polynomial", @@ -1623,24 +1581,24 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_maybe_rayon" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ddd0330f34f0b92a9f0b29bc3f8494b30d596ab8b951233ec90b2d72ab132c" +checksum = "06fc0f984e585ea984a766c5b58d6bf6c51e463b0a0835b0dd4652d358b506b3" [[package]] name = "crate_crypto_internal_eth_kzg_polynomial" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7488314261926373e1c20121c404fabf5b57ca09f48eddc7fef38be1df79a006" +checksum = "56dff7a45e2d80308b21abdbc5520ec23c3ebfb3a94fafc02edfa7f356af6d7f" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", ] [[package]] name = "crate_crypto_kzg_multi_open_fk20" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24efdb64e7518848f11069dd9de23bd04455146a9fd5486345d99ed8bfdb049" +checksum = "1a0c2f82695a88809e713e1ff9534cb90ceffab0a08f4bd33245db711f9d356f" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_maybe_rayon", @@ -1841,7 +1799,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1889,7 +1847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1911,7 +1869,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1934,23 +1892,17 @@ dependencies = [ "libc", ] -[[package]] -name = "dary_heap" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" - [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "data-encoding-macro" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b16d9d0d88a5273d830dac8b78ceb217ffc9b1d5404e5597a3542515329405b" +checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1958,9 +1910,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" +checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1977,9 +1929,9 @@ dependencies = [ "environment", "hex", "serde", - "slog", "store", "strum", + "tracing", "types", ] @@ -2077,20 +2029,20 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2099,7 +2051,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -2110,55 +2071,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", - "unicode-xid", + "syn 2.0.100", ] [[package]] -name = "diesel" -version = "2.2.7" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04001f23ba8843dc315804fa324000376084dfb1c30794ff68dd279e6e5696d5" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "bitflags 2.8.0", - "byteorder", - "diesel_derives", - "itoa", - "pq-sys", - "r2d2", -] - -[[package]] -name = "diesel_derives" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" -dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "diesel_migrations" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" -dependencies = [ - "syn 2.0.98", + "syn 2.0.100", + "unicode-xid", ] [[package]] @@ -2200,16 +2125,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -2221,17 +2136,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "discv5" version = "0.9.1" @@ -2273,7 +2177,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2286,32 +2190,18 @@ dependencies = [ "futures", "logging", "parking_lot 0.12.3", - "slog", "slot_clock", "task_executor", "tokio", + "tracing", "types", ] -[[package]] -name = "dsl_auto_type" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" -dependencies = [ - "darling 0.20.10", - "either", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dunce" @@ -2370,6 +2260,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "ef_tests" version = "0.2.0" @@ -2404,9 +2306,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -2436,7 +2338,7 @@ dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.5", "digest 0.10.7", - "ff 0.13.0", + "ff 0.13.1", "generic-array", "group 0.13.0", "pem-rfc7468", @@ -2484,7 +2386,27 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -2514,37 +2436,29 @@ dependencies = [ name = "environment" version = "0.1.2" dependencies = [ - "async-channel", + "async-channel 1.9.0", + "clap", "ctrlc", "eth2_config", "eth2_network_config", "futures", "logging", + "logroller", "serde", - "slog", - "slog-async", - "slog-json", - "slog-term", - "sloggers", "task_executor", "tokio", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "types", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -2574,12 +2488,11 @@ dependencies = [ "sensitive_url", "serde", "serde_yaml", - "slog", - "sloggers", "state_processing", "superstruct", "task_executor", "tokio", + "tracing", "tree_hash", "types", ] @@ -2605,14 +2518,16 @@ version = "0.1.0" dependencies = [ "derivative", "either", + "enr", "eth2_keystore", "ethereum_serde_utils", "ethereum_ssz", "ethereum_ssz_derive", "futures", "futures-util", - "lighthouse_network", + "libp2p-identity", "mediatype", + "multiaddr", "pretty_reqwest_error", "proto_array", "reqwest", @@ -2622,7 +2537,6 @@ dependencies = [ "serde_json", "slashing_protection", "ssz_types", - "store", "tokio", "types", "zeroize", @@ -2656,7 +2570,7 @@ dependencies = [ "bls", "hex", "num-bigint-dig", - "ring 0.16.20", + "ring", "sha2 0.9.9", "zeroize", ] @@ -2692,15 +2606,14 @@ dependencies = [ "eth2_config", "ethereum_ssz", "kzg", - "logging", "pretty_reqwest_error", "reqwest", "sensitive_url", "serde_yaml", "sha2 0.9.9", - "slog", "tempfile", "tokio", + "tracing", "types", "url", "zip", @@ -2828,7 +2741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ "cpufeatures", - "ring 0.17.8", + "ring", "sha2 0.10.8", ] @@ -2847,25 +2760,30 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e999563461faea0ab9bc0024e5e66adcee35881f3d5062f52f31a4070fe1522" +checksum = "86da3096d1304f5f28476ce383005385459afeaf0eea08592b65ddbc9b258d16" dependencies = [ "alloy-primitives", + "arbitrary", + "ethereum_serde_utils", "itertools 0.13.0", + "serde", + "serde_derive", "smallvec", + "typenum", ] [[package]] name = "ethereum_ssz_derive" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3deae99c8e74829a00ba7a92d49055732b3c1f093f2ccfa3cbc621679b6fa91" +checksum = "d832a5c38eba0e7ad92592f7a22d693954637fbb332b4f669590d66a5c3183e5" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2907,7 +2825,7 @@ dependencies = [ "serde", "serde_json", "syn 1.0.109", - "toml 0.5.11", + "toml", "url", "walkdir", ] @@ -3033,7 +2951,7 @@ dependencies = [ name = "execution_engine_integration" version = "0.1.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "deposit_contract", "ethers-core", "ethers-providers", @@ -3063,7 +2981,6 @@ dependencies = [ "builder_client", "bytes", "eth2", - "eth2_network_config", "ethereum_serde_utils", "ethereum_ssz", "ethers-core", @@ -3087,7 +3004,6 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", - "slog", "slot_clock", "ssz_types", "state_processing", @@ -3097,6 +3013,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "tree_hash", "tree_hash_derive", "triehash", @@ -3177,9 +3094,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "bitvec 1.0.1", "rand_core 0.6.4", @@ -3250,9 +3167,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "libz-sys", @@ -3293,12 +3210,13 @@ dependencies = [ "beacon_chain", "ethereum_ssz", "ethereum_ssz_derive", + "logging", "metrics", "proto_array", - "slog", "state_processing", "store", "tokio", + "tracing", "types", ] @@ -3410,7 +3328,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3420,7 +3338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.22", + "rustls 0.23.23", "rustls-pki-types", ] @@ -3441,10 +3359,6 @@ name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] [[package]] name = "futures-util" @@ -3508,12 +3422,13 @@ dependencies = [ "ethereum_ssz", "futures", "int_to_bytes", + "logging", "merkle_proof", "rayon", "sensitive_url", - "slog", "state_processing", "tokio", + "tracing", "tree_hash", "types", ] @@ -3576,7 +3491,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3585,48 +3500,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gossipsub" -version = "0.5.0" -dependencies = [ - "async-channel", - "asynchronous-codec", - "base64 0.21.7", - "byteorder", - "bytes", - "either", - "fnv", - "futures", - "futures-timer", - "getrandom 0.2.15", - "hashlink 0.9.1", - "hex_fmt", - "libp2p", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "quickcheck", - "rand 0.8.5", - "regex", - "serde", - "sha2 0.10.8", - "tracing", - "void", - "web-time", -] - [[package]] name = "graffiti_file" version = "0.1.0" @@ -3634,8 +3507,8 @@ dependencies = [ "bls", "hex", "serde", - "slog", "tempfile", + "tracing", "types", ] @@ -3656,7 +3529,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff 0.13.1", "rand 0.8.5", "rand_core 0.6.4", "rand_xorshift", @@ -3675,7 +3548,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -3684,17 +3557,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", - "indexmap 2.7.1", + "http 1.3.0", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -3848,6 +3721,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" + [[package]] name = "hex" version = "0.4.3" @@ -3882,7 +3761,7 @@ dependencies = [ "once_cell", "rand 0.9.0", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tokio", "tracing", @@ -3891,9 +3770,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.4" +version = "0.25.0-alpha.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc352e4412fb657e795f79b4efcf2bd60b59ee5ca0187f3554194cd1107a27" +checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" dependencies = [ "cfg-if", "futures-util", @@ -3902,10 +3781,10 @@ dependencies = [ "moka", "once_cell", "parking_lot 0.12.3", - "rand 0.8.5", + "rand 0.9.0", "resolv-conf", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -3992,9 +3871,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "0a761d192fbf18bdef69f5ceedd0d1333afcbda0ee23840373b8317570d23c65" dependencies = [ "bytes", "fnv", @@ -4019,18 +3898,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.0", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.0", "http-body 1.0.1", "pin-project-lite", ] @@ -4068,7 +3947,6 @@ dependencies = [ "sensitive_url", "serde", "serde_json", - "slog", "slot_clock", "state_processing", "store", @@ -4077,6 +3955,7 @@ dependencies = [ "task_executor", "tokio", "tokio-stream", + "tracing", "tree_hash", "types", "warp", @@ -4096,10 +3975,10 @@ dependencies = [ "metrics", "reqwest", "serde", - "slog", "slot_clock", "store", "tokio", + "tracing", "types", "warp", "warp_utils", @@ -4107,9 +3986,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -4156,8 +4035,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", - "http 1.2.0", + "h2 0.4.8", + "http 1.3.0", "http-body 1.0.1", "httparse", "httpdate", @@ -4204,7 +4083,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", @@ -4352,7 +4231,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4425,7 +4304,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.2.0", + "http 1.3.0", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -4446,7 +4325,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.2.0", + "http 1.3.0", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -4472,7 +4351,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", ] [[package]] @@ -4510,7 +4389,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4531,9 +4410,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "arbitrary", "equivalent", @@ -4558,8 +4437,8 @@ dependencies = [ "serde", "serde_json", "signing_method", - "slog", "tokio", + "tracing", "types", "url", "validator_dir", @@ -4569,9 +4448,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -4637,11 +4516,11 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.0", "libc", "windows-sys 0.59.0", ] @@ -4681,9 +4560,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" @@ -4706,14 +4585,14 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "js-sys", "pem", - "ring 0.17.8", + "ring", "serde", "serde_json", "simple_asn1", @@ -4800,7 +4679,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -4811,7 +4690,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lcli" -version = "6.0.1" +version = "7.0.0-beta.5" dependencies = [ "account_utils", "beacon_chain", @@ -4836,10 +4715,11 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "sloggers", "snap", "state_processing", "store", + "tracing", + "tracing-subscriber", "tree_hash", "types", "validator_dir", @@ -4870,33 +4750,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libflate" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" -dependencies = [ - "adler32", - "core2", - "crc32fast", - "dary_heap", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" -dependencies = [ - "core2", - "hashbrown 0.14.5", - "rle-decode-fast", -] +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -4921,7 +4777,7 @@ source = "git+https://github.com/sigp/libmdbx-rs?rev=e6ff4b9377c1619bcf0bfdf52be dependencies = [ "bitflags 1.3.2", "byteorder", - "derive_more 0.99.18", + "derive_more 0.99.19", "indexmap 1.9.3", "libc", "mdbx-sys", @@ -4958,7 +4814,7 @@ dependencies = [ "multiaddr", "pin-project", "rw-stream-sink", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5003,7 +4859,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "rw-stream-sink", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "unsigned-varint 0.8.0", "web-time", @@ -5025,6 +4881,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "libp2p-gossipsub" +version = "0.49.0" +source = "git+https://github.com/sigp/rust-libp2p.git?rev=7a36e4c#7a36e4cde83041f1bd5f2078c4d3934ccb16777e" +dependencies = [ + "async-channel 2.3.1", + "asynchronous-codec", + "base64 0.22.1", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "getrandom 0.2.15", + "hashlink 0.9.1", + "hex_fmt", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "sha2 0.10.8", + "tracing", + "web-time", +] + [[package]] name = "libp2p-identify" version = "0.46.0" @@ -5042,7 +4928,7 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5140,7 +5026,7 @@ dependencies = [ "rand 0.8.5", "snow", "static_assertions", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "x25519-dalek", "zeroize", @@ -5176,10 +5062,10 @@ dependencies = [ "libp2p-tls", "quinn", "rand 0.8.5", - "ring 0.17.8", - "rustls 0.23.22", + "ring", + "rustls 0.23.23", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -5216,7 +5102,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5237,19 +5123,19 @@ dependencies = [ [[package]] name = "libp2p-tls" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaebc1069dea12c5b86a597eaaddae0317c2c2cb9ec99dc94f82fd340f5c78b" +checksum = "42bbf5084fb44133267ad4caaa72a253d68d709edd2ed1cf9b42431a8ead8fd5" dependencies = [ "futures", "futures-rustls", "libp2p-core", "libp2p-identity", "rcgen", - "ring 0.17.8", - "rustls 0.23.22", + "ring", + "rustls 0.23.23", "rustls-webpki 0.101.7", - "thiserror 2.0.11", + "thiserror 2.0.12", "x509-parser", "yasna", ] @@ -5278,7 +5164,7 @@ dependencies = [ "either", "futures", "libp2p-core", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "yamux 0.12.1", "yamux 0.13.4", @@ -5290,7 +5176,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "libc", ] @@ -5366,7 +5252,7 @@ dependencies = [ [[package]] name = "lighthouse" -version = "6.0.1" +version = "7.0.0-beta.5" dependencies = [ "account_manager", "account_utils", @@ -5397,10 +5283,11 @@ dependencies = [ "serde_yaml", "slasher", "slashing_protection", - "slog", "store", "task_executor", "tempfile", + "tracing", + "tracing-subscriber", "types", "unused_port", "validator_client", @@ -5415,21 +5302,22 @@ version = "0.2.0" dependencies = [ "alloy-primitives", "alloy-rlp", - "async-channel", + "async-channel 1.9.0", "bytes", "delay_map", "directory", "dirs", "discv5", "either", + "eth2", "ethereum_ssz", "ethereum_ssz_derive", "fnv", "futures", - "gossipsub", "hex", "itertools 0.10.5", "libp2p", + "libp2p-gossipsub", "libp2p-mplex", "lighthouse_version", "local-ip-address", @@ -5445,9 +5333,6 @@ dependencies = [ "regex", "serde", "sha2 0.9.9", - "slog", - "slog-async", - "slog-term", "smallvec", "snap", "ssz_types", @@ -5459,10 +5344,11 @@ dependencies = [ "tokio", "tokio-io-timeout", "tokio-util", + "tracing", + "tracing-subscriber", "types", "unsigned-varint 0.8.0", "unused_port", - "void", ] [[package]] @@ -5487,10 +5373,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "litemap" -version = "0.7.4" +name = "linux-raw-sys" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lmdb-rkv" @@ -5545,23 +5437,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "logging" version = "0.2.0" dependencies = [ "chrono", + "logroller", "metrics", + "once_cell", "parking_lot 0.12.3", "serde", "serde_json", - "slog", - "slog-term", - "sloggers", - "take_mut", "tokio", "tracing", "tracing-appender", @@ -5570,6 +5460,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "logroller" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8dd932139da44917b3cd5812ed9536d985aa67203778e0507347579499f49c" +dependencies = [ + "chrono", + "flate2", + "regex", + "thiserror 1.0.69", +] + [[package]] name = "loom" version = "0.7.2" @@ -5647,22 +5549,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - [[package]] name = "mdbx-sys" version = "0.11.6-4" @@ -5676,9 +5562,9 @@ dependencies = [ [[package]] name = "mediatype" -version = "0.19.18" +version = "0.19.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" +checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" [[package]] name = "memchr" @@ -5737,36 +5623,15 @@ dependencies = [ "prometheus", ] -[[package]] -name = "migrations_internals" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" -dependencies = [ - "serde", - "toml 0.8.19", -] - -[[package]] -name = "migrations_macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] - [[package]] name = "milhouse" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68e33f98199224d1073f7c1468ea6abfea30736306fb79c7181a881e97ea32f" +checksum = "eb1ada1f56cc1c79f40517fdcbf57e19f60424a3a1ce372c3fe9b22e4fdd83eb" dependencies = [ "alloy-primitives", "arbitrary", - "derivative", + "educe", "ethereum_hashing", "ethereum_ssz", "ethereum_ssz_derive", @@ -5805,9 +5670,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -5831,21 +5696,21 @@ checksum = "9366861eb2a2c436c20b12c8dbec5f798cea6b47ad99216be0282942e2c81ea0" [[package]] name = "mockito" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2" +checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" dependencies = [ "assert-json-diff", "bytes", "colored", "futures-util", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", "hyper-util", "log", - "rand 0.8.5", + "rand 0.9.0", "regex", "serde_json", "serde_urlencoded", @@ -5869,7 +5734,7 @@ dependencies = [ "smallvec", "tagptr", "thiserror 1.0.69", - "uuid 1.12.1", + "uuid 1.15.1", ] [[package]] @@ -5885,10 +5750,10 @@ dependencies = [ "sensitive_url", "serde", "serde_json", - "slog", "store", "task_executor", "tokio", + "tracing", ] [[package]] @@ -5953,9 +5818,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -6041,7 +5906,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6064,7 +5929,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "anyhow", - "async-channel", + "async-channel 1.9.0", "beacon_chain", "beacon_processor", "bls", @@ -6077,12 +5942,12 @@ dependencies = [ "fnv", "futures", "genesis", - "gossipsub", "hex", "igd-next 0.16.0", "itertools 0.10.5", "k256 0.13.4", "kzg", + "libp2p-gossipsub", "lighthouse_network", "logging", "lru_cache", @@ -6093,10 +5958,6 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "serde_json", - "slog", - "slog-async", - "slog-term", - "sloggers", "slot_clock", "smallvec", "ssz_types", @@ -6105,6 +5966,8 @@ dependencies = [ "task_executor", "tokio", "tokio-stream", + "tracing", + "tracing-subscriber", "types", ] @@ -6136,7 +5999,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", @@ -6288,9 +6151,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "oneshot_broadcast" @@ -6301,9 +6164,9 @@ dependencies = [ [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -6338,11 +6201,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -6359,7 +6222,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6370,18 +6233,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.1+3.4.0" +version = "300.4.2+3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" +checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -6455,15 +6318,17 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" dependencies = [ "arrayvec", "bitvec 1.0.1", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.6.12", + "parity-scale-codec-derive 3.7.4", + "rustversion", "serde", ] @@ -6481,14 +6346,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -6540,7 +6405,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.10", "smallvec", "windows-targets 0.52.6", ] @@ -6585,9 +6450,9 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64 0.22.1", "serde", @@ -6615,7 +6480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] @@ -6629,42 +6494,24 @@ dependencies = [ "rustc_version 0.4.1", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6701,9 +6548,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platforms" @@ -6779,38 +6626,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[package]] -name = "postgres-protocol" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator", - "hmac 0.12.1", - "md-5", - "memchr", - "rand 0.9.0", - "sha2 0.10.8", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "powerfmt" @@ -6820,21 +6638,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", -] - -[[package]] -name = "pq-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b51d65ebe1cb1f40641b15abae017fed35ccdda46e3dab1ff8768f625a3222" -dependencies = [ - "libc", - "vcpkg", + "zerocopy 0.8.23", ] [[package]] @@ -6847,12 +6655,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6903,18 +6711,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.23", + "toml_edit 0.22.24", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -6945,7 +6753,6 @@ dependencies = [ "lazy_static", "memchr", "parking_lot 0.12.3", - "protobuf", "thiserror 1.0.69", ] @@ -6969,18 +6776,18 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.8.0", + "bitflags 2.9.0", "lazy_static", "num-traits", "rand 0.8.5", @@ -7000,7 +6807,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7016,12 +6823,6 @@ dependencies = [ "types", ] -[[package]] -name = "protobuf" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" - [[package]] name = "psutil" version = "3.3.0" @@ -7030,7 +6831,7 @@ checksum = "5e617cc9058daa5e1fe5a0d23ed745773a5ee354111dad1ec0235b0cc16b6730" dependencies = [ "cfg-if", "darwin-libproc", - "derive_more 0.99.18", + "derive_more 0.99.19", "glob", "mach2", "nix 0.24.3", @@ -7101,10 +6902,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", - "rustls 0.23.22", + "rustc-hash 2.1.1", + "rustls 0.23.23", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -7118,12 +6919,12 @@ dependencies = [ "bytes", "getrandom 0.2.15", "rand 0.8.5", - "ring 0.17.8", - "rustc-hash 2.1.0", - "rustls 0.23.22", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.23", "rustls-pki-types", "slab", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -7131,9 +6932,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ "cfg_aliases", "libc", @@ -7145,9 +6946,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -7204,8 +7005,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.14", + "rand_core 0.9.3", + "zerocopy 0.8.23", ] [[package]] @@ -7225,7 +7026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -7239,12 +7040,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.14", ] [[package]] @@ -7278,12 +7078,13 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.11.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ "pem", - "ring 0.16.20", + "ring", + "rustls-pki-types", "time", "yasna", ] @@ -7308,11 +7109,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -7400,7 +7201,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper", "system-configuration 0.5.1", "tokio", "tokio-native-tls", @@ -7465,40 +7266,18 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - [[package]] name = "rlp" version = "0.5.2" @@ -7559,9 +7338,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" dependencies = [ "alloy-rlp", "arbitrary", @@ -7573,7 +7352,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", "primitive-types 0.12.2", "proptest", "rand 0.8.5", @@ -7606,9 +7385,9 @@ dependencies = [ [[package]] name = "rust_eth_kzg" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a237a478ee68e491a0f40bbcbb958b79ba9b37aacce459f7ab3ba78f3cbfa9d0" +checksum = "3f83b5559e1dcd3f7721838909288faf4500fb466eff98eac99b67ac04335b93" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_erasure_codes", @@ -7632,9 +7411,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -7657,7 +7436,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.25", + "semver 1.0.26", ] [[package]] @@ -7689,13 +7468,26 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.2", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -7703,7 +7495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] @@ -7715,7 +7507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -7724,12 +7516,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.22" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -7769,8 +7561,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -7779,16 +7571,16 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rusty-fork" @@ -7815,9 +7607,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "safe_arith" @@ -7849,7 +7641,7 @@ checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", "derive_more 1.0.0", - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", "scale-info-derive", ] @@ -7859,10 +7651,10 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7913,8 +7705,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -7951,7 +7743,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -7979,9 +7771,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -7995,12 +7787,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -8017,9 +7803,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -8036,20 +7822,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -8057,34 +7843,15 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", + "syn 2.0.100", ] [[package]] @@ -8099,35 +7866,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "ryu", "serde", @@ -8263,9 +8008,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" @@ -8275,7 +8020,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -8285,25 +8030,22 @@ version = "0.2.0" dependencies = [ "clap", "env_logger 0.9.3", + "environment", "eth2_network_config", "execution_layer", "futures", "kzg", + "logging", "node_test_rig", "parking_lot 0.12.3", "rayon", "sensitive_url", "serde_json", "tokio", + "tracing-subscriber", "types", ] -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" version = "0.4.9" @@ -8327,7 +8069,6 @@ dependencies = [ "libmdbx", "lmdb-rkv", "lmdb-rkv-sys", - "logging", "lru", "maplit", "metrics", @@ -8337,10 +8078,10 @@ dependencies = [ "redb", "safe_arith", "serde", - "slog", "ssz_types", "strum", "tempfile", + "tracing", "tree_hash", "tree_hash_derive", "types", @@ -8355,11 +8096,11 @@ dependencies = [ "lighthouse_network", "network", "slasher", - "slog", "slot_clock", "state_processing", "task_executor", "tokio", + "tracing", "types", ] @@ -8377,111 +8118,10 @@ dependencies = [ "serde", "serde_json", "tempfile", + "tracing", "types", ] -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" -dependencies = [ - "erased-serde", -] - -[[package]] -name = "slog-async" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" -dependencies = [ - "crossbeam-channel", - "slog", - "take_mut", - "thread_local", -] - -[[package]] -name = "slog-json" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" -dependencies = [ - "serde", - "serde_json", - "slog", - "time", -] - -[[package]] -name = "slog-kvfilter" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" -dependencies = [ - "regex", - "slog", -] - -[[package]] -name = "slog-scope" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" -dependencies = [ - "arc-swap", - "lazy_static", - "slog", -] - -[[package]] -name = "slog-stdlog" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" -dependencies = [ - "log", - "slog", - "slog-scope", -] - -[[package]] -name = "slog-term" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" -dependencies = [ - "is-terminal", - "slog", - "term", - "thread_local", - "time", -] - -[[package]] -name = "sloggers" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75062c2738b82cd45ae633623caae3393f43eb00aada1dc2d3ebe88db6b0db9b" -dependencies = [ - "chrono", - "libc", - "libflate", - "once_cell", - "regex", - "serde", - "slog", - "slog-async", - "slog-json", - "slog-kvfilter", - "slog-scope", - "slog-stdlog", - "slog-term", - "trackable", - "winapi", - "windows-acl", -] - [[package]] name = "slot_clock" version = "0.2.0" @@ -8493,9 +8133,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" dependencies = [ "arbitrary", ] @@ -8517,7 +8157,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek", "rand_core 0.6.4", - "ring 0.17.8", + "ring", "rustc_version 0.4.1", "sha2 0.10.8", "subtle", @@ -8533,12 +8173,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -8567,12 +8201,11 @@ dependencies = [ [[package]] name = "ssz_types" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e0719d2b86ac738a55ae71a8429f52aa2741da988f1fd0975b4cc610fd1e08" +checksum = "dad0fa7e9a85c06d0a6ba5100d733fff72e231eb6db2d86078225cf716fd2d95" dependencies = [ "arbitrary", - "derivative", "ethereum_serde_utils", "ethereum_ssz", "itertools 0.13.0", @@ -8655,27 +8288,16 @@ dependencies = [ "redb", "safe_arith", "serde", - "slog", - "sloggers", "smallvec", "state_processing", "strum", "superstruct", "tempfile", + "tracing", + "tracing-subscriber", "types", "xdelta3", - "zstd 0.13.2", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", + "zstd 0.13.3", ] [[package]] @@ -8755,9 +8377,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -8770,12 +8392,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - [[package]] name = "synstructure" version = "0.13.1" @@ -8784,7 +8400,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8819,7 +8435,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -8861,12 +8477,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tap" version = "1.0.1" @@ -8890,41 +8500,27 @@ checksum = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe" name = "task_executor" version = "0.1.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "futures", - "logging", "metrics", - "slog", - "sloggers", "tokio", "tracing", ] [[package]] name = "tempfile" -version = "3.16.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 0.38.44", + "rustix 1.0.2", "windows-sys 0.59.0", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -8936,22 +8532,14 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.2", "windows-sys 0.59.0", ] -[[package]] -name = "test-test_logger" -version = "0.1.0" -dependencies = [ - "logging", - "slog", -] - [[package]] name = "test_random_derive" version = "0.2.0" @@ -8960,23 +8548,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "testcontainers" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" -dependencies = [ - "bollard-stubs", - "futures", - "hex", - "hmac 0.12.1", - "log", - "rand 0.8.5", - "serde", - "serde_json", - "sha2 0.10.8", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -8988,11 +8559,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -9003,18 +8574,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9069,9 +8640,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -9084,15 +8655,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -9103,10 +8674,10 @@ name = "timer" version = "0.2.0" dependencies = [ "beacon_chain", - "slog", "slot_clock", "task_executor", "tokio", + "tracing", ] [[package]] @@ -9159,9 +8730,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -9174,9 +8745,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", @@ -9208,7 +8779,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9221,32 +8792,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot 0.12.3", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.9.0", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -9304,26 +8849,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.23", -] - [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -9331,46 +8861,22 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.23" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", + "indexmap 2.8.0", "toml_datetime", - "winnow 0.7.0", + "winnow 0.7.3", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -9409,7 +8915,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9443,6 +8949,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -9453,54 +8969,40 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", -] - -[[package]] -name = "trackable" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" -dependencies = [ - "trackable_derive", -] - -[[package]] -name = "trackable_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" -dependencies = [ - "quote", - "syn 1.0.109", + "tracing-serde", ] [[package]] name = "tree_hash" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373495c23db675a5192de8b610395e1bec324d596f9e6111192ce903dc11403a" +checksum = "6c58eb0f518840670270d90d97ffee702d8662d9c5494870c9e1e9e0fa00f668" dependencies = [ "alloy-primitives", "ethereum_hashing", + "ethereum_ssz", "smallvec", + "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0857056ca4eb5de8c417309be42bcff6017b47e86fbaddde609b4633f66061e" +checksum = "699e7fb6b3fdfe0c809916f251cf5132d64966858601695c3736630a87e7166a" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9531,9 +9033,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "types" @@ -9558,7 +9060,6 @@ dependencies = [ "int_to_bytes", "itertools 0.10.5", "kzg", - "log", "maplit", "merkle_proof", "metastruct", @@ -9575,7 +9076,6 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "slog", "smallvec", "ssz_types", "state_processing", @@ -9584,6 +9084,7 @@ dependencies = [ "tempfile", "test_random_derive", "tokio", + "tracing", "tree_hash", "tree_hash_derive", ] @@ -9636,17 +9137,11 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -9657,12 +9152,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -9702,12 +9191,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -9763,11 +9246,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.12.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.1", ] [[package]] @@ -9794,9 +9277,9 @@ dependencies = [ "sensitive_url", "serde", "slashing_protection", - "slog", "slot_clock", "tokio", + "tracing", "types", "validator_http_api", "validator_http_metrics", @@ -9848,9 +9331,9 @@ dependencies = [ "rand 0.8.5", "sensitive_url", "serde", + "serde_json", "signing_method", "slashing_protection", - "slog", "slot_clock", "sysinfo", "system_health", @@ -9858,6 +9341,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "types", "url", "validator_dir", @@ -9874,12 +9358,13 @@ version = "0.1.0" dependencies = [ "health_metrics", "lighthouse_version", + "logging", "malloc_utils", "metrics", "parking_lot 0.12.3", "serde", - "slog", "slot_clock", + "tracing", "types", "validator_metrics", "validator_services", @@ -9932,11 +9417,12 @@ dependencies = [ "eth2", "futures", "graffiti_file", + "logging", "parking_lot 0.12.3", "safe_arith", - "slog", "slot_clock", "tokio", + "tracing", "tree_hash", "types", "validator_metrics", @@ -9950,13 +9436,14 @@ dependencies = [ "account_utils", "doppelganger_service", "initialized_validators", + "logging", "parking_lot 0.12.3", "serde", "signing_method", "slashing_protection", - "slog", "slot_clock", "task_executor", + "tracing", "types", "validator_metrics", ] @@ -9966,12 +9453,11 @@ name = "validator_test_rig" version = "0.1.0" dependencies = [ "eth2", - "logging", "mockito", "regex", "sensitive_url", "serde_json", - "slog", + "tracing", "types", ] @@ -9999,17 +9485,11 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -10069,7 +9549,6 @@ dependencies = [ "bytes", "eth2", "headers", - "metrics", "safe_arith", "serde", "serde_array_query", @@ -10094,12 +9573,6 @@ dependencies = [ "wit-bindgen-rt", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -10122,7 +9595,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -10157,7 +9630,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10199,40 +9672,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "watch" -version = "0.1.0" -dependencies = [ - "axum", - "beacon_chain", - "beacon_node", - "bls", - "clap", - "clap_utils", - "diesel", - "diesel_migrations", - "env_logger 0.9.3", - "eth2", - "http_api", - "hyper 1.6.0", - "log", - "logging", - "network", - "r2d2", - "rand 0.8.5", - "reqwest", - "serde", - "serde_json", - "serde_yaml", - "task_executor", - "testcontainers", - "tokio", - "tokio-postgres", - "types", - "unused_port", - "url", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -10258,7 +9697,7 @@ name = "web3signer_tests" version = "0.1.0" dependencies = [ "account_utils", - "async-channel", + "async-channel 1.9.0", "environment", "eth2_keystore", "eth2_network_config", @@ -10299,17 +9738,6 @@ dependencies = [ "rustix 0.38.44", ] -[[package]] -name = "whoami" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" -dependencies = [ - "redox_syscall 0.5.8", - "wasite", - "web-sys", -] - [[package]] name = "widestring" version = "0.4.3" @@ -10425,7 +9853,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -10436,9 +9864,15 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.1.2" @@ -10692,9 +10126,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -10715,7 +10149,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -10742,7 +10176,7 @@ dependencies = [ "log", "pharos", "rustc_version 0.4.1", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", @@ -10796,7 +10230,7 @@ dependencies = [ [[package]] name = "xdelta3" version = "0.1.5" -source = "git+http://github.com/sigp/xdelta3-rs?rev=50d63cdf1878e5cf3538e9aae5eed34a22c64e4a#50d63cdf1878e5cf3538e9aae5eed34a22c64e4a" +source = "git+http://github.com/sigp/xdelta3-rs?rev=4db64086bb02e9febb584ba93b9d16bb2ae3825a#4db64086bb02e9febb584ba93b9d16bb2ae3825a" dependencies = [ "bindgen", "cc", @@ -10893,7 +10327,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -10903,17 +10337,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" -version = "0.8.14" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.14", + "zerocopy-derive 0.8.23", ] [[package]] @@ -10924,38 +10357,38 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerocopy-derive" -version = "0.8.14" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -10977,7 +10410,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -10999,7 +10432,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -11033,11 +10466,11 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe 7.2.1", + "zstd-safe 7.2.3", ] [[package]] @@ -11052,18 +10485,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 73912f6082..5284713fc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "beacon_node/http_api", "beacon_node/http_metrics", "beacon_node/lighthouse_network", - "beacon_node/lighthouse_network/gossipsub", "beacon_node/network", "beacon_node/operation_pool", "beacon_node/store", @@ -85,7 +84,6 @@ members = [ "testing/node_test_rig", "testing/simulator", "testing/state_transition_vectors", - "testing/test-test_logger", "testing/validator_test_rig", "testing/web3signer_tests", @@ -104,8 +102,6 @@ members = [ "validator_client/validator_store", "validator_manager", - - "watch", ] resolver = "2" @@ -134,13 +130,13 @@ delay_map = "0.4" derivative = "2" dirs = "3" either = "1.9" -rust_eth_kzg = "0.5.3" +rust_eth_kzg = "0.5.4" discv5 = { version = "0.9", features = ["libp2p"] } env_logger = "0.9" ethereum_hashing = "0.7.0" ethereum_serde_utils = "0.7" -ethereum_ssz = "0.7" -ethereum_ssz_derive = "0.7" +ethereum_ssz = "0.8.2" +ethereum_ssz_derive = "0.8.2" ethers-core = "1" ethers-providers = { version = "1", default-features = false } exit-future = "0.2" @@ -148,20 +144,22 @@ fnv = "1" fs2 = "0.4" futures = "0.3" graffiti_file = { path = "validator_client/graffiti_file" } +gossipsub = { package = "libp2p-gossipsub", git = "https://github.com/sigp/rust-libp2p.git", rev = "7a36e4c" } hex = "0.4" hashlink = "0.9.0" hyper = "1" itertools = "0.10" libsecp256k1 = "0.7" log = "0.4" +logroller = "0.1.4" lru = "0.12" maplit = "1" -milhouse = "0.3" +milhouse = "0.5" mockito = "1.5.0" num_cpus = "1" parking_lot = "0.12" paste = "1" -prometheus = "0.13" +prometheus = { version = "0.13", default-features = false } quickcheck = "1" quickcheck_macros = "1" quote = "1" @@ -176,7 +174,7 @@ reqwest = { version = "0.11", default-features = false, features = [ "rustls-tls", "native-tls-vendored", ] } -ring = "0.16" +ring = "0.17" rpds = "0.11" rusqlite = { version = "0.28", features = ["bundled"] } serde = { version = "1", features = ["derive"] } @@ -184,17 +182,9 @@ serde_json = "1" serde_repr = "0.1" serde_yaml = "0.9" sha2 = "0.9" -slog = { version = "2", features = [ - "max_level_debug", - "release_max_level_debug", - "nested-values", -] } -slog-async = "2" -slog-term = "2" -sloggers = { version = "2", features = ["json"] } smallvec = { version = "1.11.2", features = ["arbitrary"] } snap = "1" -ssz_types = "0.8" +ssz_types = "0.10" strum = { version = "0.24", features = ["derive"] } superstruct = "0.8" syn = "1" @@ -212,9 +202,9 @@ tracing = "0.1.40" tracing-appender = "0.2" tracing-core = "0.1" tracing-log = "0.2" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tree_hash = "0.8" -tree_hash_derive = "0.8" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +tree_hash = "0.9" +tree_hash_derive = "0.9" url = "2" uuid = { version = "0.8", features = ["serde", "v4"] } warp = { version = "0.3.7", default-features = false, features = ["tls"] } @@ -248,7 +238,6 @@ fixed_bytes = { path = "consensus/fixed_bytes" } filesystem = { path = "common/filesystem" } fork_choice = { path = "consensus/fork_choice" } genesis = { path = "beacon_node/genesis" } -gossipsub = { path = "beacon_node/lighthouse_network/gossipsub/" } health_metrics = { path = "common/health_metrics" } http_api = { path = "beacon_node/http_api" } initialized_validators = { path = "validator_client/initialized_validators" } @@ -289,7 +278,7 @@ validator_metrics = { path = "validator_client/validator_metrics" } validator_store = { path = "validator_client/validator_store" } validator_test_rig = { path = "testing/validator_test_rig" } warp_utils = { path = "common/warp_utils" } -xdelta3 = { git = "http://github.com/sigp/xdelta3-rs", rev = "50d63cdf1878e5cf3538e9aae5eed34a22c64e4a" } +xdelta3 = { git = "http://github.com/sigp/xdelta3-rs", rev = "4db64086bb02e9febb584ba93b9d16bb2ae3825a" } zstd = "0.13" [profile.maxperf] diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 7da65ad742..cf963535c7 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "beacon_node" -version = "6.0.1" +version = "7.0.0-beta.5" authors = [ "Paul Hauner ", "Age Manning BeaconChain { epoch: Epoch, validators: Vec, ) -> Result { - debug!(self.log, "computing attestation rewards"; "epoch" => epoch, "validator_count" => validators.len()); + debug!( + %epoch, + validator_count = validators.len(), + "computing attestation rewards" + ); // Get state let state_slot = (epoch + 1).end_slot(T::EthSpec::slots_per_epoch()); @@ -47,8 +51,10 @@ impl BeaconChain { .state_root_at_slot(state_slot)? .ok_or(BeaconChainError::NoStateForSlot(state_slot))?; + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let state = self - .get_state(&state_root, Some(state_slot))? + .get_state(&state_root, Some(state_slot), true)? .ok_or(BeaconChainError::MissingBeaconState(state_root))?; if state.fork_name_unchecked().altair_enabled() { @@ -214,10 +220,9 @@ impl BeaconChain { // Return 0s for unknown/inactive validator indices. let Ok(validator) = state.get_validator(validator_index) else { debug!( - self.log, - "No rewards for inactive/unknown validator"; - "index" => validator_index, - "epoch" => previous_epoch + index = validator_index, + epoch = %previous_epoch, + "No rewards for inactive/unknown validator" ); total_rewards.push(TotalAttestationRewards { validator_index: validator_index as u64, diff --git a/beacon_node/beacon_chain/src/attestation_simulator.rs b/beacon_node/beacon_chain/src/attestation_simulator.rs index c97c4490af..59d316578b 100644 --- a/beacon_node/beacon_chain/src/attestation_simulator.rs +++ b/beacon_node/beacon_chain/src/attestation_simulator.rs @@ -1,9 +1,9 @@ use crate::{BeaconChain, BeaconChainTypes}; -use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::time::sleep; +use tracing::{debug, error}; use types::{EthSpec, Slot}; /// Don't run the attestation simulator if the head slot is this many epochs @@ -36,10 +36,7 @@ async fn attestation_simulator_service( Some(duration) => { sleep(duration + additional_delay).await; - debug!( - chain.log, - "Simulating unagg. attestation production"; - ); + debug!("Simulating unagg. attestation production"); // Run the task in the executor let inner_chain = chain.clone(); @@ -53,7 +50,7 @@ async fn attestation_simulator_service( ); } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } @@ -85,10 +82,9 @@ pub fn produce_unaggregated_attestation( let data = unaggregated_attestation.data(); debug!( - chain.log, - "Produce unagg. attestation"; - "attestation_source" => data.source.root.to_string(), - "attestation_target" => data.target.root.to_string(), + attestation_source = data.source.root.to_string(), + attestation_target = data.target.root.to_string(), + "Produce unagg. attestation" ); chain @@ -98,9 +94,8 @@ pub fn produce_unaggregated_attestation( } Err(e) => { debug!( - chain.log, - "Failed to simulate attestation"; - "error" => ?e + error = ?e, + "Failed to simulate attestation" ); } } diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index a70a2caa4f..6f1174c1ba 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -43,7 +43,6 @@ use crate::{ use bls::verify_signature_sets; use itertools::Itertools; use proto_array::Block as ProtoBlock; -use slog::debug; use slot_clock::SlotClock; use state_processing::{ common::{ @@ -58,6 +57,7 @@ use state_processing::{ }; use std::borrow::Cow; use strum::AsRefStr; +use tracing::debug; use tree_hash::TreeHash; use types::{ Attestation, AttestationData, AttestationRef, BeaconCommittee, @@ -430,10 +430,9 @@ fn process_slash_info( Ok((indexed, _)) => (indexed, true, err), Err(e) => { debug!( - chain.log, - "Unable to obtain indexed form of attestation for slasher"; - "attestation_root" => format!("{:?}", attestation.tree_hash_root()), - "error" => format!("{:?}", e) + attestation_root = ?attestation.tree_hash_root(), + error = ?e, + "Unable to obtain indexed form of attestation for slasher" ); return err; } @@ -447,9 +446,8 @@ fn process_slash_info( if check_signature { if let Err(e) = verify_attestation_signature(chain, &indexed_attestation) { debug!( - chain.log, - "Signature verification for slasher failed"; - "error" => format!("{:?}", e), + error = ?e, + "Signature verification for slasher failed" ); return err; } @@ -1128,6 +1126,12 @@ fn verify_head_block_is_known( } } + if !verify_attestation_is_finalized_checkpoint_or_descendant(attestation.data(), chain) { + return Err(Error::HeadBlockFinalized { + beacon_block_root: attestation.data().beacon_block_root, + }); + } + Ok(block) } else if chain.is_pre_finalization_block(attestation.data().beacon_block_root)? { Err(Error::HeadBlockFinalized { @@ -1361,6 +1365,29 @@ pub fn verify_committee_index(attestation: AttestationRef) -> Res Ok(()) } +fn verify_attestation_is_finalized_checkpoint_or_descendant( + attestation_data: &AttestationData, + chain: &BeaconChain, +) -> bool { + // If we have a split block newer than finalization then we also ban attestations which are not + // descended from that split block. It's important not to try checking `is_descendant` if + // finality is ahead of the split and the split block has been pruned, as `is_descendant` will + // return `false` in this case. + let fork_choice = chain.canonical_head.fork_choice_read_lock(); + let attestation_block_root = attestation_data.beacon_block_root; + let finalized_slot = fork_choice + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()); + let split = chain.store.get_split_info(); + let is_descendant_from_split_block = split.slot == 0 + || split.slot <= finalized_slot + || fork_choice.is_descendant(split.block_root, attestation_block_root); + + fork_choice.is_finalized_checkpoint_or_descendant(attestation_block_root) + && is_descendant_from_split_block +} + /// Assists in readability. type CommitteesPerSlot = u64; @@ -1450,19 +1477,17 @@ where return Err(Error::UnknownTargetRoot(target.root)); } - chain - .with_committee_cache(target.root, attestation_epoch, |committee_cache, _| { - let committees_per_slot = committee_cache.committees_per_slot(); + chain.with_committee_cache(target.root, attestation_epoch, |committee_cache, _| { + let committees_per_slot = committee_cache.committees_per_slot(); - Ok(committee_cache - .get_beacon_committees_at_slot(attestation.data().slot) - .map(|committees| map_fn((committees, committees_per_slot))) - .unwrap_or_else(|_| { - Err(Error::NoCommitteeForSlotAndIndex { - slot: attestation.data().slot, - index: attestation.committee_index().unwrap_or(0), - }) - })) - }) - .map_err(BeaconChainError::from)? + Ok(committee_cache + .get_beacon_committees_at_slot(attestation.data().slot) + .map(|committees| map_fn((committees, committees_per_slot))) + .unwrap_or_else(|_| { + Err(Error::NoCommitteeForSlotAndIndex { + slot: attestation.data().slot, + index: attestation.committee_index().unwrap_or(0), + }) + })) + })? } diff --git a/beacon_node/beacon_chain/src/attester_cache.rs b/beacon_node/beacon_chain/src/attester_cache.rs index 7f356bd621..ae715afcd0 100644 --- a/beacon_node/beacon_chain/src/attester_cache.rs +++ b/beacon_node/beacon_chain/src/attester_cache.rs @@ -325,8 +325,10 @@ impl AttesterCache { return Ok(value); } + // We use `cache_state = true` here because if we are attesting to the state it's likely + // to be recent and useful for other things. let mut state: BeaconState = chain - .get_state(&state_root, None)? + .get_state(&state_root, None, true)? .ok_or(Error::MissingBeaconState(state_root))?; if state.slot() > slot { diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index e0bb79bf38..8808a3f121 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -1,8 +1,7 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes, StateSkipConfig}; use attesting_indices_base::get_attesting_indices; -use eth2::lighthouse::StandardBlockReward; +use eth2::types::StandardBlockReward; use safe_arith::SafeArith; -use slog::error; use state_processing::common::attesting_indices_base; use state_processing::{ common::{ @@ -19,6 +18,7 @@ use store::{ consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}, RelativeEpoch, }; +use tracing::error; use types::{AbstractExecPayload, BeaconBlockRef, BeaconState, BeaconStateError, EthSpec}; type BeaconBlockSubRewardValue = u64; @@ -56,9 +56,8 @@ impl BeaconChain { .compute_beacon_block_proposer_slashing_reward(block, state) .map_err(|e| { error!( - self.log, - "Error calculating proposer slashing reward"; - "error" => ?e + error = ?e, + "Error calculating proposer slashing reward" ); BeaconChainError::BlockRewardError })?; @@ -67,9 +66,8 @@ impl BeaconChain { .compute_beacon_block_attester_slashing_reward(block, state) .map_err(|e| { error!( - self.log, - "Error calculating attester slashing reward"; - "error" => ?e + error = ?e, + "Error calculating attester slashing reward" ); BeaconChainError::BlockRewardError })?; @@ -78,9 +76,8 @@ impl BeaconChain { self.compute_beacon_block_attestation_reward_base(block, state) .map_err(|e| { error!( - self.log, - "Error calculating base block attestation reward"; - "error" => ?e + error = ?e, + "Error calculating base block attestation reward" ); BeaconChainError::BlockRewardAttestationError })? @@ -88,9 +85,8 @@ impl BeaconChain { self.compute_beacon_block_attestation_reward_altair_deneb(block, state) .map_err(|e| { error!( - self.log, - "Error calculating altair block attestation reward"; - "error" => ?e + error = ?e, + "Error calculating altair block attestation reward" ); BeaconChainError::BlockRewardAttestationError })? diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index 32ec776868..e37a69040d 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -1,6 +1,6 @@ use crate::{metrics, BeaconChain, BeaconChainError, BeaconChainTypes, BlockProcessStatus}; use execution_layer::{ExecutionLayer, ExecutionPayloadBodyV1}; -use slog::{crit, debug, error, Logger}; +use logging::crit; use std::collections::HashMap; use std::sync::Arc; use store::{DatabaseBlock, ExecutionPayloadDeneb}; @@ -9,6 +9,7 @@ use tokio::sync::{ RwLock, }; use tokio_stream::{wrappers::UnboundedReceiverStream, Stream}; +use tracing::{debug, error}; use types::{ ChainSpec, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, @@ -129,7 +130,6 @@ fn reconstruct_default_header_block( fn reconstruct_blocks( block_map: &mut HashMap>>, block_parts_with_bodies: HashMap>, - log: &Logger, ) { for (root, block_parts) in block_parts_with_bodies { if let Some(payload_body) = block_parts.body { @@ -156,7 +156,7 @@ fn reconstruct_blocks( reconstructed_transactions_root: header_from_payload .transactions_root(), }; - debug!(log, "Failed to reconstruct block"; "root" => ?root, "error" => ?error); + debug!(?root, ?error, "Failed to reconstruct block"); block_map.insert(root, Arc::new(Err(error))); } } @@ -232,7 +232,7 @@ impl BodiesByRange { } } - async fn execute(&mut self, execution_layer: &ExecutionLayer, log: &Logger) { + async fn execute(&mut self, execution_layer: &ExecutionLayer) { if let RequestState::UnSent(blocks_parts_ref) = &mut self.state { let block_parts_vec = std::mem::take(blocks_parts_ref); @@ -261,12 +261,12 @@ impl BodiesByRange { }); } - reconstruct_blocks(&mut block_map, with_bodies, log); + reconstruct_blocks(&mut block_map, with_bodies); } Err(e) => { let block_result = Arc::new(Err(Error::BlocksByRangeFailure(Box::new(e)).into())); - debug!(log, "Payload bodies by range failure"; "error" => ?block_result); + debug!(error = ?block_result, "Payload bodies by range failure"); for block_parts in block_parts_vec { block_map.insert(block_parts.root(), block_result.clone()); } @@ -280,9 +280,8 @@ impl BodiesByRange { &mut self, root: &Hash256, execution_layer: &ExecutionLayer, - log: &Logger, ) -> Option>> { - self.execute(execution_layer, log).await; + self.execute(execution_layer).await; if let RequestState::Sent(map) = &self.state { return map.get(root).cloned(); } @@ -313,7 +312,7 @@ impl EngineRequest { } } - pub async fn push_block_parts(&mut self, block_parts: BlockParts, log: &Logger) { + pub async fn push_block_parts(&mut self, block_parts: BlockParts) { match self { Self::ByRange(bodies_by_range) => { let mut request = bodies_by_range.write().await; @@ -327,28 +326,21 @@ impl EngineRequest { Self::NoRequest(_) => { // this should _never_ happen crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "push_block_parts called on NoRequest Variant", + beacon_block_streamer = "push_block_parts called on NoRequest Variant", + "Please notify the devs" ); } } } - pub async fn push_block_result( - &mut self, - root: Hash256, - block_result: BlockResult, - log: &Logger, - ) { + pub async fn push_block_result(&mut self, root: Hash256, block_result: BlockResult) { // this function will only fail if something is seriously wrong match self { Self::ByRange(_) => { // this should _never_ happen crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "push_block_result called on ByRange", + beacon_block_streamer = "push_block_result called on ByRange", + "Please notify the devs" ); } Self::NoRequest(results) => { @@ -361,24 +353,22 @@ impl EngineRequest { &self, root: &Hash256, execution_layer: &ExecutionLayer, - log: &Logger, ) -> Arc> { match self { Self::ByRange(by_range) => { by_range .write() .await - .get_block_result(root, execution_layer, log) + .get_block_result(root, execution_layer) .await } Self::NoRequest(map) => map.read().await.get(root).cloned(), } .unwrap_or_else(|| { crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "block_result not found in request", - "root" => ?root, + beacon_block_streamer = "block_result not found in request", + ?root, + "Please notify the devs" ); Arc::new(Err(Error::BlockResultNotFound.into())) }) @@ -518,9 +508,7 @@ impl BeaconBlockStreamer { } }; - no_request - .push_block_result(root, block_result, &self.beacon_chain.log) - .await; + no_request.push_block_result(root, block_result).await; requests.insert(root, no_request.clone()); } @@ -529,9 +517,7 @@ impl BeaconBlockStreamer { by_range_blocks.sort_by_key(|block_parts| block_parts.slot()); for block_parts in by_range_blocks { let root = block_parts.root(); - by_range - .push_block_parts(block_parts, &self.beacon_chain.log) - .await; + by_range.push_block_parts(block_parts).await; requests.insert(root, by_range.clone()); } @@ -541,17 +527,12 @@ impl BeaconBlockStreamer { result.push((root, request.clone())) } else { crit!( - self.beacon_chain.log, - "Please notify the devs"; - "beacon_block_streamer" => "request not found", - "root" => ?root, + beacon_block_streamer = "request not found", + ?root, + "Please notify the devs" ); no_request - .push_block_result( - root, - Err(Error::RequestNotFound.into()), - &self.beacon_chain.log, - ) + .push_block_result(root, Err(Error::RequestNotFound.into())) .await; result.push((root, no_request.clone())); } @@ -566,10 +547,7 @@ impl BeaconBlockStreamer { block_roots: Vec, sender: UnboundedSender<(Hash256, Arc>)>, ) { - debug!( - self.beacon_chain.log, - "Using slower fallback method of eth_getBlockByHash()" - ); + debug!("Using slower fallback method of eth_getBlockByHash()"); for root in block_roots { let cached_block = self.check_caches(root); let block_result = if cached_block.is_some() { @@ -601,9 +579,8 @@ impl BeaconBlockStreamer { Ok(payloads) => payloads, Err(e) => { error!( - self.beacon_chain.log, - "BeaconBlockStreamer: Failed to load payloads"; - "error" => ?e + error = ?e, + "BeaconBlockStreamer: Failed to load payloads" ); return; } @@ -615,9 +592,7 @@ impl BeaconBlockStreamer { engine_requests += 1; } - let result = request - .get_block_result(&root, &self.execution_layer, &self.beacon_chain.log) - .await; + let result = request.get_block_result(&root, &self.execution_layer).await; let successful = result .as_ref() @@ -636,13 +611,12 @@ impl BeaconBlockStreamer { } debug!( - self.beacon_chain.log, - "BeaconBlockStreamer finished"; - "requested blocks" => n_roots, - "sent" => n_sent, - "succeeded" => n_success, - "failed" => (n_sent - n_success), - "engine requests" => engine_requests, + requested_blocks = n_roots, + sent = n_sent, + succeeded = n_success, + failed = (n_sent - n_success), + engine_requests, + "BeaconBlockStreamer finished" ); } @@ -678,9 +652,8 @@ impl BeaconBlockStreamer { ) -> impl Stream>)> { let (block_tx, block_rx) = mpsc::unbounded_channel(); debug!( - self.beacon_chain.log, - "Launching a BeaconBlockStreamer"; - "blocks" => block_roots.len(), + blocks = block_roots.len(), + "Launching a BeaconBlockStreamer" ); let executor = self.beacon_chain.task_executor.clone(); executor.spawn(self.stream(block_roots, block_tx), "get_blocks_sender"); @@ -732,7 +705,6 @@ mod tests { let harness = BeaconChainHarness::builder(MinimalEthSpec) .spec(spec) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(logging::test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .build(); diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6d46aaabe8..9692441aba 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -21,8 +21,8 @@ use crate::block_verification_types::{ pub use crate::canonical_head::CanonicalHead; use crate::chain_config::ChainConfig; use crate::data_availability_checker::{ - Availability, AvailabilityCheckError, AvailableBlock, DataAvailabilityChecker, - DataColumnReconstructionResult, + Availability, AvailabilityCheckError, AvailableBlock, AvailableBlockData, + DataAvailabilityChecker, DataColumnReconstructionResult, }; use crate::data_column_verification::{GossipDataColumnError, GossipVerifiedDataColumn}; use crate::early_attester_cache::EarlyAttesterCache; @@ -42,7 +42,7 @@ use crate::light_client_optimistic_update_verification::{ Error as LightClientOptimisticUpdateError, VerifiedLightClientOptimisticUpdate, }; use crate::light_client_server_cache::LightClientServerCache; -use crate::migrate::BackgroundMigrator; +use crate::migrate::{BackgroundMigrator, ManualFinalizationNotification}; use crate::naive_aggregation_pool::{ AggregatedAttestationMap, Error as NaiveAggregationError, NaiveAggregationPool, SyncContributionAggregateMap, @@ -86,6 +86,7 @@ use futures::channel::mpsc::Sender; use itertools::process_results; use itertools::Itertools; use kzg::Kzg; +use logging::crit; use operation_pool::{ CompactAttestationRef, OperationPool, PersistedOperationPool, ReceivedPreCapella, }; @@ -93,7 +94,6 @@ use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use proto_array::{DoNotReOrg, ProposerHeadError}; use safe_arith::SafeArith; use slasher::Slasher; -use slog::{crit, debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; use state_processing::{ @@ -118,12 +118,13 @@ use std::sync::Arc; use std::time::Duration; use store::iter::{BlockRootsIterator, ParentRootBlockIterator, StateRootsIterator}; use store::{ - BlobSidecarListFromRoot, DatabaseBlock, Error as DBError, HotColdDB, KeyValueStore, - KeyValueStoreOp, StoreItem, StoreOp, + BlobSidecarListFromRoot, DatabaseBlock, Error as DBError, HotColdDB, HotStateSummary, + KeyValueStore, KeyValueStoreOp, StoreItem, StoreOp, }; use task_executor::{ShutdownReason, TaskExecutor}; use tokio::sync::oneshot; use tokio_stream::Stream; +use tracing::{debug, error, info, trace, warn}; use tree_hash::TreeHash; use types::blob_sidecar::FixedBlobSidecarList; use types::data_column_sidecar::{ColumnIndex, DataColumnIdentifier}; @@ -144,11 +145,6 @@ pub const FORK_CHOICE_DB_KEY: Hash256 = Hash256::ZERO; /// Defines how old a block can be before it's no longer a candidate for the early attester cache. const EARLY_ATTESTER_CACHE_HISTORIC_SLOTS: u64 = 4; -/// Defines a distance between the head block slot and the current slot. -/// -/// If the head block is older than this value, don't bother preparing beacon proposers. -const PREPARE_PROPOSER_HISTORIC_EPOCHS: u64 = 4; - /// If the head is more than `MAX_PER_SLOT_FORK_CHOICE_DISTANCE` slots behind the wall-clock slot, DO NOT /// run the per-slot tasks (primarily fork choice). /// @@ -485,8 +481,6 @@ pub struct BeaconChain { /// Sender given to tasks, so that if they encounter a state in which execution cannot /// continue they can request that everything shuts down. pub shutdown_sender: Sender, - /// Logging to CLI, etc. - pub(crate) log: Logger, /// Arbitrary bytes included in the blocks. pub(crate) graffiti_calculator: GraffitiCalculator, /// Optional slasher. @@ -671,7 +665,6 @@ impl BeaconChain { store: BeaconStore, reset_payload_statuses: ResetPayloadStatuses, spec: &ChainSpec, - log: &Logger, ) -> Result>, Error> { let Some(persisted_fork_choice) = store.get_item::(&FORK_CHOICE_DB_KEY)? @@ -687,7 +680,6 @@ impl BeaconChain { reset_payload_statuses, fc_store, spec, - log, )?)) } @@ -820,8 +812,10 @@ impl BeaconChain { let block = self .get_blinded_block(&block_root)? .ok_or(Error::MissingBeaconBlock(block_root))?; + // This method is only used in tests, so we may as well cache states to make CI go brr. + // TODO(release-v7) move this method out of beacon chain and into `store_tests`` or something equivalent. let state = self - .get_state(&block.state_root(), Some(block.slot()))? + .get_state(&block.state_root(), Some(block.slot()), true)? .ok_or_else(|| Error::MissingBeaconState(block.state_root()))?; let iter = BlockRootsIterator::owned(&self.store, state); Ok(std::iter::once(Ok((block_root, block.slot()))) @@ -1218,9 +1212,8 @@ impl BeaconChain { if header_from_payload != execution_payload_header { for txn in execution_payload.transactions() { debug!( - self.log, - "Reconstructed txn"; - "bytes" => format!("0x{}", hex::encode(&**txn)), + bytes = format!("0x{}", hex::encode(&**txn)), + "Reconstructed txn" ); } @@ -1348,8 +1341,9 @@ impl BeaconChain { &self, state_root: &Hash256, slot: Option, + update_cache: bool, ) -> Result>, Error> { - Ok(self.store.get_state(state_root, slot)?) + Ok(self.store.get_state(state_root, slot, update_cache)?) } /// Return the sync committee at `slot + 1` from the canonical chain. @@ -1435,7 +1429,6 @@ impl BeaconChain { slot, &parent_root, &sync_aggregate, - &self.log, &self.spec, ) } @@ -1481,10 +1474,9 @@ impl BeaconChain { Ordering::Greater => { if slot > head_state.slot() + T::EthSpec::slots_per_epoch() { warn!( - self.log, - "Skipping more than an epoch"; - "head_slot" => head_state.slot(), - "request_slot" => slot + head_slot = %head_state.slot(), + request_slot = %slot, + "Skipping more than an epoch" ) } @@ -1503,11 +1495,10 @@ impl BeaconChain { Ok(_) => (), Err(e) => { warn!( - self.log, - "Unable to load state at slot"; - "error" => ?e, - "head_slot" => head_state_slot, - "requested_slot" => slot + error = ?e, + head_slot= %head_state_slot, + requested_slot = %slot, + "Unable to load state at slot" ); return Err(Error::NoStateForSlot(slot)); } @@ -1524,8 +1515,14 @@ impl BeaconChain { })? .ok_or(Error::NoStateForSlot(slot))?; + // This branch is mostly reached from the HTTP API when doing analysis, or in niche + // situations when producing a block. In the HTTP API case we assume the user wants + // to cache states so that future calls are faster, and that if the cache is + // struggling due to non-finality that they will dial down inessential calls. In the + // block proposal case we want to cache the state so that we can process the block + // quickly after it has been signed. Ok(self - .get_state(&state_root, Some(slot))? + .get_state(&state_root, Some(slot), true)? .ok_or(Error::NoStateForSlot(slot))?) } } @@ -1707,6 +1704,45 @@ impl BeaconChain { } } + pub fn manually_compact_database(&self) { + self.store_migrator.process_manual_compaction(); + } + + pub fn manually_finalize_state( + &self, + state_root: Hash256, + checkpoint: Checkpoint, + ) -> Result<(), Error> { + let HotStateSummary { + slot, + latest_block_root, + .. + } = self + .store + .load_hot_state_summary(&state_root) + .map_err(BeaconChainError::DBError)? + .ok_or(BeaconChainError::MissingHotStateSummary(state_root))?; + + if slot != checkpoint.epoch.start_slot(T::EthSpec::slots_per_epoch()) + || latest_block_root != *checkpoint.root + { + return Err(BeaconChainError::InvalidCheckpoint { + state_root, + checkpoint, + }); + } + + let notif = ManualFinalizationNotification { + state_root: state_root.into(), + checkpoint, + head_tracker: self.head_tracker.clone(), + genesis_block_root: self.genesis_block_root, + }; + + self.store_migrator.process_manual_finalization(notif); + Ok(()) + } + /// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`. /// /// The attestation will be obtained from `self.naive_aggregation_pool`. @@ -1871,9 +1907,8 @@ impl BeaconChain { // The cache returned an error. Log the error and proceed with the rest of this // function. Err(e) => warn!( - self.log, - "Early attester cache failed"; - "error" => ?e + error = ?e, + "Early attester cache failed" ), } @@ -2018,11 +2053,10 @@ impl BeaconChain { cached_values } else { debug!( - self.log, - "Attester cache miss"; - "beacon_block_root" => ?beacon_block_root, - "head_state_slot" => %head_state_slot, - "request_slot" => %request_slot, + ?beacon_block_root, + %head_state_slot, + %request_slot, + "Attester cache miss" ); // Neither the head state, nor the attester cache was able to produce the required @@ -2282,30 +2316,27 @@ impl BeaconChain { match self.naive_aggregation_pool.write().insert(attestation) { Ok(outcome) => trace!( - self.log, - "Stored unaggregated attestation"; - "outcome" => ?outcome, - "index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), + ?outcome, + index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "Stored unaggregated attestation" ), Err(NaiveAggregationError::SlotTooLow { slot, lowest_permissible_slot, }) => { trace!( - self.log, - "Refused to store unaggregated attestation"; - "lowest_permissible_slot" => lowest_permissible_slot.as_u64(), - "slot" => slot.as_u64(), + lowest_permissible_slot = lowest_permissible_slot.as_u64(), + slot = slot.as_u64(), + "Refused to store unaggregated attestation" ); } Err(e) => { error!( - self.log, - "Failed to store unaggregated attestation"; - "error" => ?e, - "index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), + error = ?e, + index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "Failed to store unaggregated attestation" ); return Err(Error::from(e).into()); } @@ -2345,30 +2376,27 @@ impl BeaconChain { .insert(&contribution) { Ok(outcome) => trace!( - self.log, - "Stored unaggregated sync committee message"; - "outcome" => ?outcome, - "index" => sync_message.validator_index, - "slot" => sync_message.slot.as_u64(), + ?outcome, + index = sync_message.validator_index, + slot = sync_message.slot.as_u64(), + "Stored unaggregated sync committee message" ), Err(NaiveAggregationError::SlotTooLow { slot, lowest_permissible_slot, }) => { trace!( - self.log, - "Refused to store unaggregated sync committee message"; - "lowest_permissible_slot" => lowest_permissible_slot.as_u64(), - "slot" => slot.as_u64(), + lowest_permissible_slot = lowest_permissible_slot.as_u64(), + slot = slot.as_u64(), + "Refused to store unaggregated sync committee message" ); } Err(e) => { error!( - self.log, - "Failed to store unaggregated sync committee message"; - "error" => ?e, - "index" => sync_message.validator_index, - "slot" => sync_message.slot.as_u64(), + error = ?e, + index = sync_message.validator_index, + slot = sync_message.slot.as_u64(), + "Failed to store unaggregated sync committee message" ); return Err(Error::from(e).into()); } @@ -2461,11 +2489,10 @@ impl BeaconChain { self.shuffling_is_compatible_result(block_root, target_epoch, state) .unwrap_or_else(|e| { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "reason" => ?e, + ?block_root, + %target_epoch, + reason = ?e, + "Skipping attestation with incompatible shuffling" ); false }) @@ -2506,11 +2533,10 @@ impl BeaconChain { } } else { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "reason" => "target epoch less than block epoch" + ?block_root, + %target_epoch, + reason = "target epoch less than block epoch", + "Skipping attestation with incompatible shuffling" ); return Ok(false); }; @@ -2519,12 +2545,11 @@ impl BeaconChain { Ok(true) } else { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "head_shuffling_id" => ?head_shuffling_id, - "block_shuffling_id" => ?block_shuffling_id, + ?block_root, + %target_epoch, + ?head_shuffling_id, + ?block_shuffling_id, + "Skipping attestation with incompatible shuffling" ); Ok(false) } @@ -2862,6 +2887,15 @@ impl BeaconChain { chain_segment: Vec>, notify_execution_layer: NotifyExecutionLayer, ) -> ChainSegmentResult { + for block in chain_segment.iter() { + if let Err(error) = self.check_invalid_block_roots(block.block_root()) { + return ChainSegmentResult::Failed { + imported_blocks: vec![], + error, + }; + } + } + let mut imported_blocks = vec![]; // Filter uninteresting blocks from the chain segment in a blocking task. @@ -2940,8 +2974,11 @@ impl BeaconChain { imported_blocks.push((block_root, block_slot)); } AvailabilityProcessingStatus::MissingComponents(slot, block_root) => { - warn!(self.log, "Blobs missing in response to range request"; - "block_root" => ?block_root, "slot" => slot); + warn!( + ?block_root, + %slot, + "Blobs missing in response to range request" + ); return ChainSegmentResult::Failed { imported_blocks, error: BlockError::AvailabilityCheck( @@ -2952,9 +2989,10 @@ impl BeaconChain { } } Err(BlockError::DuplicateFullyImported(block_root)) => { - debug!(self.log, - "Ignoring already known blocks while processing chain segment"; - "block_root" => ?block_root); + debug!( + ?block_root, + "Ignoring already known blocks while processing chain segment" + ); continue; } Err(error) => { @@ -2977,7 +3015,7 @@ impl BeaconChain { // TODO(das): update fork-choice, act on sampling result, adjust log level // NOTE: It is possible that sampling complets before block is imported into fork choice, // in that case we may need to update availability cache. - info!(self.log, "Sampling completed"; "block_root" => %block_root); + info!(%block_root, "Sampling completed"); } /// Returns `Ok(GossipVerifiedBlock)` if the supplied `block` should be forwarded onto the @@ -2993,6 +3031,7 @@ impl BeaconChain { pub async fn verify_block_for_gossip( self: &Arc, block: Arc>, + custody_columns_count: usize, ) -> Result, BlockError> { let chain = self.clone(); self.task_executor @@ -3002,27 +3041,25 @@ impl BeaconChain { let slot = block.slot(); let graffiti_string = block.message().body().graffiti().as_utf8_lossy(); - match GossipVerifiedBlock::new(block, &chain) { + match GossipVerifiedBlock::new(block, &chain, custody_columns_count) { Ok(verified) => { let commitments_formatted = verified.block.commitments_formatted(); debug!( - chain.log, - "Successfully verified gossip block"; - "graffiti" => graffiti_string, - "slot" => slot, - "root" => ?verified.block_root(), - "commitments" => commitments_formatted, + graffiti = graffiti_string, + %slot, + root = ?verified.block_root(), + commitments = commitments_formatted, + "Successfully verified gossip block" ); Ok(verified) } Err(e) => { debug!( - chain.log, - "Rejected gossip block"; - "error" => e.to_string(), - "graffiti" => graffiti_string, - "slot" => slot, + error = e.to_string(), + graffiti = graffiti_string, + %slot, + "Rejected gossip block" ); Err(e) @@ -3169,7 +3206,14 @@ impl BeaconChain { return Err(BlockError::DuplicateFullyImported(block_root)); } - self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref)); + // process_engine_blobs is called for both pre and post PeerDAS. However, post PeerDAS + // consumers don't expect the blobs event to fire erratically. + if !self + .spec + .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) + { + self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref)); + } let r = self .check_engine_blob_availability_and_import(slot, block_root, blobs, data_column_recv) @@ -3344,6 +3388,15 @@ impl BeaconChain { self.remove_notified(&block_root, r) } + /// Check for known and configured invalid block roots before processing. + pub fn check_invalid_block_roots(&self, block_root: Hash256) -> Result<(), BlockError> { + if self.config.invalid_block_roots.contains(&block_root) { + Err(BlockError::KnownInvalidExecutionPayload(block_root)) + } else { + Ok(()) + } + } + /// Returns `Ok(block_root)` if the given `unverified_block` was successfully verified and /// imported into the chain. /// @@ -3426,11 +3479,10 @@ impl BeaconChain { // The block was successfully verified and imported. Yay. Ok(status @ AvailabilityProcessingStatus::Imported(block_root)) => { debug!( - self.log, - "Beacon block imported"; - "block_root" => ?block_root, - "block_slot" => block_slot, - "source" => %block_source, + ?block_root, + %block_slot, + source = %block_source, + "Beacon block imported" ); // Increment the Prometheus counter for block processing successes. @@ -3439,20 +3491,14 @@ impl BeaconChain { Ok(status) } Ok(status @ AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { - debug!( - self.log, - "Beacon block awaiting blobs"; - "block_root" => ?block_root, - "block_slot" => slot, - ); + debug!(?block_root, %slot, "Beacon block awaiting blobs"); Ok(status) } Err(e @ BlockError::BeaconChainError(BeaconChainError::TokioJoin(_))) => { debug!( - self.log, - "Beacon block processing cancelled"; - "error" => ?e, + error = ?e, + "Beacon block processing cancelled" ); Err(e) } @@ -3460,19 +3506,14 @@ impl BeaconChain { // be partially verified or partially imported. Err(BlockError::BeaconChainError(e)) => { crit!( - self.log, - "Beacon block processing error"; - "error" => ?e, + error = ?e, + "Beacon block processing error" ); Err(BlockError::BeaconChainError(e)) } // The block failed verification. Err(other) => { - debug!( - self.log, - "Beacon block rejected"; - "reason" => other.to_string(), - ); + debug!(reason = other.to_string(), "Beacon block rejected"); Err(other) } } @@ -3499,31 +3540,24 @@ impl BeaconChain { // Log the PoS pandas if a merge transition just occurred. if payload_verification_outcome.is_valid_merge_transition_block { - info!(self.log, "{}", POS_PANDA_BANNER); + info!("{}", POS_PANDA_BANNER); + info!(slot = %block.slot(), "Proof of Stake Activated"); info!( - self.log, - "Proof of Stake Activated"; - "slot" => block.slot() + terminal_pow_block_hash = ?block + .message() + .execution_payload()? + .parent_hash() + .into_root(), ); info!( - self.log, ""; - "Terminal POW Block Hash" => ?block - .message() - .execution_payload()? - .parent_hash() - .into_root() + merge_transition_block_root = ?block.message().tree_hash_root(), ); info!( - self.log, ""; - "Merge Transition Block Root" => ?block.message().tree_hash_root() - ); - info!( - self.log, ""; - "Merge Transition Execution Hash" => ?block - .message() - .execution_payload()? - .block_hash() - .into_root() + merge_transition_execution_hash = ?block + .message() + .execution_payload()? + .block_hash() + .into_root(), ); } Ok(ExecutedBlock::new( @@ -3640,9 +3674,12 @@ impl BeaconChain { data_column_recv: Option>>, ) -> Result { self.check_blobs_for_slashability(block_root, &blobs)?; - let availability = - self.data_availability_checker - .put_engine_blobs(block_root, blobs, data_column_recv)?; + let availability = self.data_availability_checker.put_engine_blobs( + block_root, + slot.epoch(T::EthSpec::slots_per_epoch()), + blobs, + data_column_recv, + )?; self.process_availability(slot, availability, || Ok(())) .await @@ -3727,7 +3764,6 @@ impl BeaconChain { parent_eth1_finalization_data, confirmed_state_roots, consensus_context, - data_column_recv, } = import_data; // Record the time at which this block's blobs became available. @@ -3755,7 +3791,6 @@ impl BeaconChain { parent_block, parent_eth1_finalization_data, consensus_context, - data_column_recv, ) }, "payload_verification_handle", @@ -3794,7 +3829,6 @@ impl BeaconChain { parent_block: SignedBlindedBeaconBlock, parent_eth1_finalization_data: Eth1FinalizationData, mut consensus_context: ConsensusContext, - data_column_recv: Option>>, ) -> Result { // ----------------------------- BLOCK NOT YET ATTESTABLE ---------------------------------- // Everything in this initial section is on the hot path between processing the block and @@ -3892,15 +3926,14 @@ impl BeaconChain { if let Some(proto_block) = fork_choice.get_block(&block_root) { if let Err(e) = self.early_attester_cache.add_head_block( block_root, - signed_block.clone(), + &signed_block, proto_block, &state, &self.spec, ) { warn!( - self.log, - "Early attester cache insert failed"; - "error" => ?e + error = ?e, + "Early attester cache insert failed" ); } else { let attestable_timestamp = @@ -3912,19 +3945,14 @@ impl BeaconChain { ) } } else { - warn!( - self.log, - "Early attester block missing"; - "block_root" => ?block_root - ); + warn!(?block_root, "Early attester block missing"); } } // This block did not become the head, nothing to do. Ok(_) => (), Err(e) => error!( - self.log, - "Failed to compute head during block import"; - "error" => ?e + error = ?e, + "Failed to compute head during block import" ), } drop(fork_choice_timer); @@ -3961,26 +3989,19 @@ impl BeaconChain { // If the write fails, revert fork choice to the version from disk, else we can // end up with blocks in fork choice that are missing from disk. // See https://github.com/sigp/lighthouse/issues/2028 - let (_, signed_block, blobs, data_columns) = signed_block.deconstruct(); + let (_, signed_block, block_data) = signed_block.deconstruct(); - match self.get_blobs_or_columns_store_op( - block_root, - signed_block.epoch(), - blobs, - data_columns, - data_column_recv, - ) { + match self.get_blobs_or_columns_store_op(block_root, block_data) { Ok(Some(blobs_or_columns_store_op)) => { ops.push(blobs_or_columns_store_op); } Ok(None) => {} Err(e) => { error!( - self.log, - "Failed to store data columns into the database"; - "msg" => "Restoring fork choice from disk", - "error" => &e, - "block_root" => ?block_root + msg = "Restoring fork choice from disk", + error = &e, + ?block_root, + "Failed to store data columns into the database" ); return Err(self .handle_import_block_db_write_error(fork_choice) @@ -4003,10 +4024,9 @@ impl BeaconChain { if let Err(e) = self.store.do_atomically_with_block_and_blobs_cache(ops) { error!( - self.log, - "Database write failed!"; - "msg" => "Restoring fork choice from disk", - "error" => ?e, + msg = "Restoring fork choice from disk", + error = ?e, + "Database write failed!" ); return Err(self .handle_import_block_db_write_error(fork_choice) @@ -4042,7 +4062,7 @@ impl BeaconChain { &mut state, ) .unwrap_or_else(|e| { - error!(self.log, "error caching light_client data {:?}", e); + error!("error caching light_client data {:?}", e); }); } @@ -4100,13 +4120,11 @@ impl BeaconChain { ), &self.store, &self.spec, - &self.log, ) { crit!( - self.log, - "No stored fork choice found to restore from"; - "error" => ?e, - "warning" => "The database is likely corrupt now, consider --purge-db" + error = ?e, + warning = "The database is likely corrupt now, consider --purge-db", + "No stored fork choice found to restore from" ); Err(BlockError::BeaconChainError(e)) } else { @@ -4145,17 +4163,15 @@ impl BeaconChain { { let mut shutdown_sender = self.shutdown_sender(); crit!( - self.log, - "Weak subjectivity checkpoint verification failed while importing block!"; - "block_root" => ?block_root, - "parent_root" => ?block.parent_root(), - "old_finalized_epoch" => ?current_head_finalized_checkpoint.epoch, - "new_finalized_epoch" => ?new_finalized_checkpoint.epoch, - "weak_subjectivity_epoch" => ?wss_checkpoint.epoch, - "error" => ?e + ?block_root, + parent_root = ?block.parent_root(), + old_finalized_epoch = ?current_head_finalized_checkpoint.epoch, + new_finalized_epoch = ?new_finalized_checkpoint.epoch, + weak_subjectivity_epoch = ?wss_checkpoint.epoch, + error = ?e, + "Weak subjectivity checkpoint verification failed while importing block!" ); crit!( - self.log, "You must use the `--purge-db` flag to clear the database and restart sync. \ You may be on a hostile network." ); @@ -4224,11 +4240,10 @@ impl BeaconChain { } Err(e) => { warn!( - self.log, - "Unable to fetch sync committee"; - "epoch" => duty_epoch, - "purpose" => "validator monitor", - "error" => ?e, + epoch = %duty_epoch, + purpose = "validator monitor", + error = ?e, + "Unable to fetch sync committee" ); } } @@ -4240,11 +4255,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "validator monitor", - "attestation_slot" => attestation.data().slot, - "error" => ?e, + purpose = "validator monitor", + attestation_slot = %attestation.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4296,10 +4310,9 @@ impl BeaconChain { Ok(_) | Err(AttestationObservationError::SlotTooLow { .. }) => {} Err(e) => { debug!( - self.log, - "Failed to register observed attestation"; - "error" => ?e, - "epoch" => a.data().target.epoch + error = ?e, + epoch = %a.data().target.epoch, + "Failed to register observed attestation" ); } } @@ -4308,11 +4321,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "observation", - "attestation_slot" => a.data().slot, - "error" => ?e, + purpose = "observation", + attestation_slot = %a.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4325,11 +4337,10 @@ impl BeaconChain { .observe_validator(a.data().target.epoch, validator_index as usize) { debug!( - self.log, - "Failed to register observed block attester"; - "error" => ?e, - "epoch" => a.data().target.epoch, - "validator_index" => validator_index, + error = ?e, + epoch = %a.data().target.epoch, + validator_index, + "Failed to register observed block attester" ) } } @@ -4349,11 +4360,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "slasher", - "attestation_slot" => attestation.data().slot, - "error" => ?e, + purpose = "slasher", + attestation_slot = %attestation.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4425,9 +4435,8 @@ impl BeaconChain { sync_aggregate.clone(), )) { warn!( - self.log, - "Failed to send light_client server event"; - "error" => ?e + error = ?e, + "Failed to send light_client server event" ); } } @@ -4444,9 +4453,8 @@ impl BeaconChain { ) { if let Err(e) = self.import_block_update_shuffling_cache_fallible(block_root, state) { warn!( - self.log, - "Failed to prime shuffling cache"; - "error" => ?e + error = ?e, + "Failed to prime shuffling cache" ); } } @@ -4522,10 +4530,9 @@ impl BeaconChain { let finalized_deposit_count = finalized_eth1_data.deposit_count; eth1_chain.finalize_eth1_data(finalized_eth1_data); debug!( - self.log, - "called eth1_chain.finalize_eth1_data()"; - "epoch" => current_finalized_checkpoint.epoch, - "deposit count" => finalized_deposit_count, + epoch = %current_finalized_checkpoint.epoch, + deposit_count = %finalized_deposit_count, + "called eth1_chain.finalize_eth1_data()" ); } } @@ -4548,36 +4555,32 @@ impl BeaconChain { match rx.wait_for_fork_choice(slot, timeout) { ForkChoiceWaitResult::Success(fc_slot) => { debug!( - self.log, - "Fork choice successfully updated before block production"; - "slot" => slot, - "fork_choice_slot" => fc_slot, + %slot, + fork_choice_slot = %fc_slot, + "Fork choice successfully updated before block production" ); } ForkChoiceWaitResult::Behind(fc_slot) => { warn!( - self.log, - "Fork choice notifier out of sync with block production"; - "fork_choice_slot" => fc_slot, - "slot" => slot, - "message" => "this block may be orphaned", + fork_choice_slot = %fc_slot, + %slot, + message = "this block may be orphaned", + "Fork choice notifier out of sync with block production" ); } ForkChoiceWaitResult::TimeOut => { warn!( - self.log, - "Timed out waiting for fork choice before proposal"; - "message" => "this block may be orphaned", + message = "this block may be orphaned", + "Timed out waiting for fork choice before proposal" ); } } } else { error!( - self.log, - "Producing block at incorrect slot"; - "block_slot" => slot, - "current_slot" => current_slot, - "message" => "check clock sync, this block may be orphaned", + %slot, + %current_slot, + message = "check clock sync, this block may be orphaned", + "Producing block at incorrect slot" ); } } @@ -4653,10 +4656,9 @@ impl BeaconChain { self.get_state_for_re_org(slot, head_slot, head_block_root) { info!( - self.log, - "Proposing block to re-org current head"; - "slot" => slot, - "head_to_reorg" => %head_block_root, + %slot, + head_to_reorg = %head_block_root, + "Proposing block to re-org current head" ); (re_org_state, Some(re_org_state_root)) } else { @@ -4671,10 +4673,9 @@ impl BeaconChain { } } else { warn!( - self.log, - "Producing block that conflicts with head"; - "message" => "this block is more likely to be orphaned", - "slot" => slot, + message = "this block is more likely to be orphaned", + %slot, + "Producing block that conflicts with head" ); let state = self .state_at_slot(slot - 1, StateSkipConfig::WithStateRoots) @@ -4702,9 +4703,8 @@ impl BeaconChain { if self.spec.proposer_score_boost.is_none() { warn!( - self.log, - "Ignoring proposer re-org configuration"; - "reason" => "this network does not have proposer boost enabled" + reason = "this network does not have proposer boost enabled", + "Ignoring proposer re-org configuration" ); return None; } @@ -4713,11 +4713,7 @@ impl BeaconChain { .slot_clock .seconds_from_current_slot_start() .or_else(|| { - warn!( - self.log, - "Not attempting re-org"; - "error" => "unable to read slot clock" - ); + warn!(error = "unable to read slot clock", "Not attempting re-org"); None })?; @@ -4728,21 +4724,13 @@ impl BeaconChain { // 3. The `get_proposer_head` conditions from fork choice pass. let proposing_on_time = slot_delay < self.config.re_org_cutoff(self.spec.seconds_per_slot); if !proposing_on_time { - debug!( - self.log, - "Not attempting re-org"; - "reason" => "not proposing on time", - ); + debug!(reason = "not proposing on time", "Not attempting re-org"); return None; } let head_late = self.block_observed_after_attestation_deadline(canonical_head, head_slot); if !head_late { - debug!( - self.log, - "Not attempting re-org"; - "reason" => "head not late" - ); + debug!(reason = "head not late", "Not attempting re-org"); return None; } @@ -4763,16 +4751,14 @@ impl BeaconChain { .map_err(|e| match e { ProposerHeadError::DoNotReOrg(reason) => { debug!( - self.log, - "Not attempting re-org"; - "reason" => %reason, + %reason, + "Not attempting re-org" ); } ProposerHeadError::Error(e) => { warn!( - self.log, - "Not attempting re-org"; - "error" => ?e, + error = ?e, + "Not attempting re-org" ); } }) @@ -4784,21 +4770,16 @@ impl BeaconChain { .store .get_advanced_hot_state_from_cache(re_org_parent_block, slot) .or_else(|| { - warn!( - self.log, - "Not attempting re-org"; - "reason" => "no state in cache" - ); + warn!(reason = "no state in cache", "Not attempting re-org"); None })?; info!( - self.log, - "Attempting re-org due to weak head"; - "weak_head" => ?canonical_head, - "parent" => ?re_org_parent_block, - "head_weight" => proposer_head.head_node.weight, - "threshold_weight" => proposer_head.re_org_head_weight_threshold + weak_head = ?canonical_head, + parent = ?re_org_parent_block, + head_weight = proposer_head.head_node.weight, + threshold_weight = proposer_head.re_org_head_weight_threshold, + "Attempting re-org due to weak head" ); Some((state, state_root)) @@ -4822,10 +4803,9 @@ impl BeaconChain { // The proposer head must be equal to the canonical head or its parent. if proposer_head != head_block_root && proposer_head != head_parent_block_root { warn!( - self.log, - "Unable to compute payload attributes"; - "block_root" => ?proposer_head, - "head_block_root" => ?head_block_root, + block_root = ?proposer_head, + head_block_root = ?head_block_root, + "Unable to compute payload attributes" ); return Ok(None); } @@ -4847,14 +4827,13 @@ impl BeaconChain { let proposer_index = if let Some(proposer) = cached_proposer { proposer.index as u64 } else { - if head_epoch + 2 < proposal_epoch { + if head_epoch + self.config.sync_tolerance_epochs < proposal_epoch { warn!( - self.log, - "Skipping proposer preparation"; - "msg" => "this is a non-critical issue that can happen on unhealthy nodes or \ + msg = "this is a non-critical issue that can happen on unhealthy nodes or \ networks.", - "proposal_epoch" => proposal_epoch, - "head_epoch" => head_epoch, + %proposal_epoch, + %head_epoch, + "Skipping proposer preparation" ); // Don't skip the head forward more than two epochs. This avoids burdening an @@ -4887,10 +4866,7 @@ impl BeaconChain { // // Exit now, after updating the cache. if decision_root != shuffling_decision_root { - warn!( - self.log, - "Head changed during proposer preparation"; - ); + warn!("Head changed during proposer preparation"); return Ok(None); } @@ -4952,10 +4928,9 @@ impl BeaconChain { // Advance the state using the partial method. debug!( - self.log, - "Advancing state for withdrawals calculation"; - "proposal_slot" => proposal_slot, - "parent_block_root" => ?parent_block_root, + %proposal_slot, + ?parent_block_root, + "Advancing state for withdrawals calculation" ); let mut advanced_state = unadvanced_state.into_owned(); partial_state_advance( @@ -4985,9 +4960,8 @@ impl BeaconChain { .or_else(|e| match e { ProposerHeadError::DoNotReOrg(reason) => { trace!( - self.log, - "Not suppressing fork choice update"; - "reason" => %reason, + %reason, + "Not suppressing fork choice update" ); Ok(canonical_forkchoice_params) } @@ -5070,10 +5044,9 @@ impl BeaconChain { .get_slot::(shuffling_decision_root, re_org_block_slot) .ok_or_else(|| { debug!( - self.log, - "Fork choice override proposer shuffling miss"; - "slot" => re_org_block_slot, - "decision_root" => ?shuffling_decision_root, + slot = %re_org_block_slot, + decision_root = ?shuffling_decision_root, + "Fork choice override proposer shuffling miss" ); DoNotReOrg::NotProposing })? @@ -5134,11 +5107,10 @@ impl BeaconChain { }; debug!( - self.log, - "Fork choice update overridden"; - "canonical_head" => ?head_block_root, - "override" => ?info.parent_node.root, - "slot" => fork_choice_slot, + canonical_head = ?head_block_root, + ?info.parent_node.root, + slot = %fork_choice_slot, + "Fork choice update overridden" ); Ok(forkchoice_update_params) @@ -5391,9 +5363,8 @@ impl BeaconChain { if let Err(e) = import(attestation) { // Don't stop block production if there's an error, just create a log. error!( - self.log, - "Attestation did not transfer to op pool"; - "reason" => ?e + reason = ?e, + "Attestation did not transfer to op pool" ); } } @@ -5441,11 +5412,10 @@ impl BeaconChain { ) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid attestation"; - "err" => ?e, - "block_slot" => state.slot(), - "attestation" => ?att + err = ?e, + block_slot = %state.slot(), + attestation = ?att, + "Attempted to include an invalid attestation" ); }) .is_ok() @@ -5457,11 +5427,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid proposer slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "slashing" => ?slashing + err = ?e, + block_slot = %state.slot(), + ?slashing, + "Attempted to include an invalid proposer slashing" ); }) .is_ok() @@ -5473,11 +5442,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid attester slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "slashing" => ?slashing + err = ?e, + block_slot = %state.slot(), + ?slashing, + "Attempted to include an invalid attester slashing" ); }) .is_ok() @@ -5488,11 +5456,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid proposer slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "exit" => ?exit + err = ?e, + block_slot = %state.slot(), + ?exit, + "Attempted to include an invalid proposer slashing" ); }) .is_ok() @@ -5510,9 +5477,8 @@ impl BeaconChain { .map_err(BlockProductionError::OpPoolError)? .unwrap_or_else(|| { warn!( - self.log, - "Producing block with no sync contributions"; - "slot" => state.slot(), + slot = %state.slot(), + "Producing block with no sync contributions" ); SyncAggregate::new() }); @@ -5832,11 +5798,7 @@ impl BeaconChain { ); let block_size = block.ssz_bytes_len(); - debug!( - self.log, - "Produced block on state"; - "block_size" => block_size, - ); + debug!(%block_size, "Produced block on state"); metrics::observe(&metrics::BLOCK_SIZE, block_size as f64); @@ -5918,11 +5880,10 @@ impl BeaconChain { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_SUCCESSES); trace!( - self.log, - "Produced beacon block"; - "parent" => ?block.parent_root(), - "attestations" => block.body().attestations_len(), - "slot" => block.slot() + parent = ?block.parent_root(), + attestations = block.body().attestations_len(), + slot = %block.slot(), + "Produced beacon block" ); Ok(BeaconBlockResponse { @@ -5945,11 +5906,7 @@ impl BeaconChain { self: &Arc, op: &InvalidationOperation, ) -> Result<(), Error> { - debug!( - self.log, - "Processing payload invalidation"; - "op" => ?op, - ); + debug!(?op, "Processing payload invalidation"); // Update the execution status in fork choice. // @@ -5972,11 +5929,10 @@ impl BeaconChain { // Update fork choice. if let Err(e) = fork_choice_result { crit!( - self.log, - "Failed to process invalid payload"; - "error" => ?e, - "latest_valid_ancestor" => ?op.latest_valid_ancestor(), - "block_root" => ?op.block_root(), + error = ?e, + latest_valid_ancestor = ?op.latest_valid_ancestor(), + block_root = ?op.block_root(), + "Failed to process invalid payload" ); } @@ -6003,10 +5959,9 @@ impl BeaconChain { if justified_block.execution_status.is_invalid() { crit!( - self.log, - "The justified checkpoint is invalid"; - "msg" => "ensure you are not connected to a malicious network. This error is not \ - recoverable, please reach out to the lighthouse developers for assistance." + msg = "ensure you are not connected to a malicious network. This error is not \ + recoverable, please reach out to the lighthouse developers for assistance.", + "The justified checkpoint is invalid" ); let mut shutdown_sender = self.shutdown_sender(); @@ -6014,10 +5969,9 @@ impl BeaconChain { INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, )) { crit!( - self.log, - "Unable to trigger client shut down"; - "msg" => "shut down may already be under way", - "error" => ?e + msg = "shut down may already be under way", + error = ?e, + "Unable to trigger client shut down" ); } @@ -6078,25 +6032,19 @@ impl BeaconChain { // Use a blocking task since blocking the core executor on the canonical head read lock can // block the core tokio executor. let chain = self.clone(); + let tolerance_slots = self.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); let maybe_prep_data = self .spawn_blocking_handle( move || { let cached_head = chain.canonical_head.cached_head(); // Don't bother with proposer prep if the head is more than - // `PREPARE_PROPOSER_HISTORIC_EPOCHS` prior to the current slot. + // `sync_tolerance_epochs` prior to the current slot. // // This prevents the routine from running during sync. let head_slot = cached_head.head_slot(); - if head_slot + T::EthSpec::slots_per_epoch() * PREPARE_PROPOSER_HISTORIC_EPOCHS - < current_slot - { - debug!( - chain.log, - "Head too old for proposer prep"; - "head_slot" => head_slot, - "current_slot" => current_slot, - ); + if head_slot + tolerance_slots < current_slot { + debug!(%head_slot, %current_slot, "Head too old for proposer prep"); return Ok(None); } @@ -6184,11 +6132,10 @@ impl BeaconChain { // Only push a log to the user if this is the first time we've seen this proposer for // this slot. info!( - self.log, - "Prepared beacon proposer"; - "prepare_slot" => prepare_slot, - "validator" => proposer, - "parent_root" => ?head_root, + %prepare_slot, + validator = proposer, + parent_root = ?head_root, + "Prepared beacon proposer" ); payload_attributes }; @@ -6218,10 +6165,9 @@ impl BeaconChain { // // This scenario might occur on an overloaded/under-resourced node. warn!( - self.log, - "Delayed proposer preparation"; - "prepare_slot" => prepare_slot, - "validator" => proposer, + %prepare_slot, + validator = proposer, + "Delayed proposer preparation" ); return Ok(None); }; @@ -6232,10 +6178,9 @@ impl BeaconChain { || till_prepare_slot <= self.config.prepare_payload_lookahead { debug!( - self.log, - "Sending forkchoiceUpdate for proposer prep"; - "till_prepare_slot" => ?till_prepare_slot, - "prepare_slot" => prepare_slot + ?till_prepare_slot, + %prepare_slot, + "Sending forkchoiceUpdate for proposer prep" ); self.update_execution_engine_forkchoice( @@ -6337,8 +6282,8 @@ impl BeaconChain { .map_err(Error::ForkchoiceUpdate)? { info!( - self.log, - "Prepared POS transition block proposer"; "slot" => next_slot + slot = %next_slot, + "Prepared POS transition block proposer" ); ( params.head_root, @@ -6396,9 +6341,8 @@ impl BeaconChain { .await?; if let Err(e) = fork_choice_update_result { error!( - self.log, - "Failed to validate payload"; - "error" => ?e + error= ?e, + "Failed to validate payload" ) }; Ok(()) @@ -6412,11 +6356,10 @@ impl BeaconChain { // error. However, we create a log to bring attention to the issue. PayloadStatus::Accepted => { warn!( - self.log, - "Fork choice update received ACCEPTED"; - "msg" => "execution engine provided an unexpected response to a fork \ + msg = "execution engine provided an unexpected response to a fork \ choice update. although this is not a serious issue, please raise \ - an issue." + an issue.", + "Fork choice update received ACCEPTED" ); Ok(()) } @@ -6425,13 +6368,12 @@ impl BeaconChain { ref validation_error, } => { warn!( - self.log, - "Invalid execution payload"; - "validation_error" => ?validation_error, - "latest_valid_hash" => ?latest_valid_hash, - "head_hash" => ?head_hash, - "head_block_root" => ?head_block_root, - "method" => "fcU", + ?validation_error, + ?latest_valid_hash, + ?head_hash, + head_block_root = ?head_block_root, + method = "fcU", + "Invalid execution payload" ); match latest_valid_hash { @@ -6479,12 +6421,11 @@ impl BeaconChain { ref validation_error, } => { warn!( - self.log, - "Invalid execution payload block hash"; - "validation_error" => ?validation_error, - "head_hash" => ?head_hash, - "head_block_root" => ?head_block_root, - "method" => "fcU", + ?validation_error, + ?head_hash, + ?head_block_root, + method = "fcU", + "Invalid execution payload block hash" ); // The execution engine has stated that the head block is invalid, however it // hasn't returned a latest valid ancestor. @@ -6505,9 +6446,9 @@ impl BeaconChain { /// Returns `true` if the given slot is prior to the `bellatrix_fork_epoch`. pub fn slot_is_prior_to_bellatrix(&self, slot: Slot) -> bool { - self.spec.bellatrix_fork_epoch.map_or(true, |bellatrix| { - slot.epoch(T::EthSpec::slots_per_epoch()) < bellatrix - }) + self.spec + .bellatrix_fork_epoch + .is_none_or(|bellatrix| slot.epoch(T::EthSpec::slots_per_epoch()) < bellatrix) } /// Returns the value of `execution_optimistic` for `block`. @@ -6599,16 +6540,19 @@ impl BeaconChain { state: &BeaconState, ) -> Result<(), BeaconChainError> { let finalized_checkpoint = state.finalized_checkpoint(); - info!(self.log, "Verifying the configured weak subjectivity checkpoint"; "weak_subjectivity_epoch" => wss_checkpoint.epoch, "weak_subjectivity_root" => ?wss_checkpoint.root); + info!( + weak_subjectivity_epoch = %wss_checkpoint.epoch, + weak_subjectivity_root = ?wss_checkpoint.root, + "Verifying the configured weak subjectivity checkpoint" + ); // If epochs match, simply compare roots. if wss_checkpoint.epoch == finalized_checkpoint.epoch && wss_checkpoint.root != finalized_checkpoint.root { crit!( - self.log, - "Root found at the specified checkpoint differs"; - "weak_subjectivity_root" => ?wss_checkpoint.root, - "finalized_checkpoint_root" => ?finalized_checkpoint.root + weak_subjectivity_root = ?wss_checkpoint.root, + finalized_checkpoint_root = ?finalized_checkpoint.root, + "Root found at the specified checkpoint differs" ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } else if wss_checkpoint.epoch < finalized_checkpoint.epoch { @@ -6622,17 +6566,18 @@ impl BeaconChain { Some(root) => { if root != wss_checkpoint.root { crit!( - self.log, - "Root found at the specified checkpoint differs"; - "weak_subjectivity_root" => ?wss_checkpoint.root, - "finalized_checkpoint_root" => ?finalized_checkpoint.root + weak_subjectivity_root = ?wss_checkpoint.root, + finalized_checkpoint_root = ?finalized_checkpoint.root, + "Root found at the specified checkpoint differs" ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } } None => { - crit!(self.log, "The root at the start slot of the given epoch could not be found"; - "wss_checkpoint_slot" => ?slot); + crit!( + wss_checkpoint_slot = ?slot, + "The root at the start slot of the given epoch could not be found" + ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } } @@ -6647,11 +6592,7 @@ impl BeaconChain { /// `tokio::runtime::block_on` in certain cases. pub async fn per_slot_task(self: &Arc) { if let Some(slot) = self.slot_clock.now() { - debug!( - self.log, - "Running beacon chain per slot tasks"; - "slot" => ?slot - ); + debug!(?slot, "Running beacon chain per slot tasks"); // Always run the light-weight pruning tasks (these structures should be empty during // sync anyway). @@ -6677,10 +6618,9 @@ impl BeaconChain { if let Some(tx) = &chain.fork_choice_signal_tx { if let Err(e) = tx.notify_fork_choice_complete(slot) { warn!( - chain.log, - "Error signalling fork choice waiter"; - "error" => ?e, - "slot" => slot, + error = ?e, + %slot, + "Error signalling fork choice waiter" ); } } @@ -6773,10 +6713,9 @@ impl BeaconChain { drop(shuffling_cache); debug!( - self.log, - "Committee cache miss"; - "shuffling_id" => ?shuffling_epoch, - "head_block_root" => head_block_root.to_string(), + shuffling_id = ?shuffling_epoch, + head_block_root = head_block_root.to_string(), + "Committee cache miss" ); // If the block's state will be so far ahead of `shuffling_epoch` that even its @@ -6903,9 +6842,11 @@ impl BeaconChain { })?; let beacon_state_root = beacon_block.state_root(); + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let mut beacon_state = self .store - .get_state(&beacon_state_root, Some(beacon_block.slot()))? + .get_state(&beacon_state_root, Some(beacon_block.slot()), true)? .ok_or_else(|| { Error::DBInconsistent(format!("Missing state {:?}", beacon_state_root)) })?; @@ -7057,8 +6998,10 @@ impl BeaconChain { if signed_beacon_block.slot() % T::EthSpec::slots_per_epoch() == 0 { let block = self.get_blinded_block(&block_hash).unwrap().unwrap(); + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let state = self - .get_state(&block.state_root(), Some(block.slot())) + .get_state(&block.state_root(), Some(block.slot()), true) .unwrap() .unwrap(); finalized_blocks.insert(state.finalized_checkpoint().root); @@ -7187,10 +7130,6 @@ impl BeaconChain { .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) } - pub fn logger(&self) -> &Logger { - &self.log - } - /// Gets the `LightClientBootstrap` object for a requested block root. /// /// Returns `None` when the state or block is not found in the database. @@ -7218,29 +7157,30 @@ impl BeaconChain { } } - fn get_blobs_or_columns_store_op( + pub(crate) fn get_blobs_or_columns_store_op( &self, block_root: Hash256, - block_epoch: Epoch, - blobs: Option>, - data_columns: Option>, - data_column_recv: Option>>, + block_data: AvailableBlockData, ) -> Result>, String> { - if self.spec.is_peer_das_enabled_for_epoch(block_epoch) { - // TODO(das) we currently store all subnet sampled columns. Tracking issue to exclude non - // custody columns: https://github.com/sigp/lighthouse/issues/6465 - let custody_columns_count = self.data_availability_checker.get_sampling_column_count(); - - let custody_columns_available = data_columns - .as_ref() - .as_ref() - .is_some_and(|columns| columns.len() == custody_columns_count); - - let data_columns_to_persist = if custody_columns_available { - // If the block was made available via custody columns received from gossip / rpc, use them - // since we already have them. - data_columns - } else if let Some(data_column_recv) = data_column_recv { + match block_data { + AvailableBlockData::NoData => Ok(None), + AvailableBlockData::Blobs(blobs) => { + debug!( + %block_root, + count = blobs.len(), + "Writing blobs to store" + ); + Ok(Some(StoreOp::PutBlobs(block_root, blobs))) + } + AvailableBlockData::DataColumns(data_columns) => { + debug!( + %block_root, + count = data_columns.len(), + "Writing data columns to store" + ); + Ok(Some(StoreOp::PutDataColumns(block_root, data_columns))) + } + AvailableBlockData::DataColumnsRecv(data_column_recv) => { // Blobs were available from the EL, in this case we wait for the data columns to be computed (blocking). let _column_recv_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DATA_COLUMNS_WAIT); @@ -7250,34 +7190,18 @@ impl BeaconChain { let computed_data_columns = data_column_recv .blocking_recv() .map_err(|e| format!("Did not receive data columns from sender: {e:?}"))?; - Some(computed_data_columns) - } else { - // No blobs in the block. - None - }; - - if let Some(data_columns) = data_columns_to_persist { - if !data_columns.is_empty() { - debug!( - self.log, "Writing data_columns to store"; - "block_root" => %block_root, - "count" => data_columns.len(), - ); - return Ok(Some(StoreOp::PutDataColumns(block_root, data_columns))); - } - } - } else if let Some(blobs) = blobs { - if !blobs.is_empty() { debug!( - self.log, "Writing blobs to store"; - "block_root" => %block_root, - "count" => blobs.len(), + %block_root, + count = computed_data_columns.len(), + "Writing data columns to store" ); - return Ok(Some(StoreOp::PutBlobs(block_root, blobs))); + // TODO(das): Store only this node's custody columns + Ok(Some(StoreOp::PutDataColumns( + block_root, + computed_data_columns, + ))) } } - - Ok(None) } } @@ -7291,15 +7215,11 @@ impl Drop for BeaconChain { if let Err(e) = drop() { error!( - self.log, - "Failed to persist on BeaconChain drop"; - "error" => ?e + error = ?e, + "Failed to persist on BeaconChain drop" ) } else { - info!( - self.log, - "Saved beacon chain to disk"; - ) + info!("Saved beacon chain to disk") } } } diff --git a/beacon_node/beacon_chain/src/bellatrix_readiness.rs b/beacon_node/beacon_chain/src/bellatrix_readiness.rs index 500588953f..412870354b 100644 --- a/beacon_node/beacon_chain/src/bellatrix_readiness.rs +++ b/beacon_node/beacon_chain/src/bellatrix_readiness.rs @@ -171,7 +171,7 @@ impl BeaconChain { return BellatrixReadiness::NotSynced; } let params = MergeConfig::from_chainspec(&self.spec); - let current_difficulty = el.get_current_difficulty().await.ok(); + let current_difficulty = el.get_current_difficulty().await.ok().flatten(); BellatrixReadiness::Ready { config: params, current_difficulty, diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index 786b627bb7..fe9d8c6bfc 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -12,9 +12,9 @@ use crate::kzg_utils::{validate_blob, validate_blobs}; use crate::observed_data_sidecars::{DoNotObserve, ObservationStrategy, Observe}; use crate::{metrics, BeaconChainError}; use kzg::{Error as KzgError, Kzg, KzgCommitment}; -use slog::debug; use ssz_derive::{Decode, Encode}; use std::time::Duration; +use tracing::debug; use tree_hash::TreeHash; use types::blob_sidecar::BlobIdentifier; use types::{ @@ -504,10 +504,9 @@ pub fn validate_blob_sidecar_for_gossip %block_root, - "index" => %blob_index, + %block_root, + %blob_index, + "Proposer shuffling cache miss for blob verification" ); let (parent_state_root, mut parent_state) = chain .store diff --git a/beacon_node/beacon_chain/src/block_times_cache.rs b/beacon_node/beacon_chain/src/block_times_cache.rs index af122ccdc0..bd1adb7e40 100644 --- a/beacon_node/beacon_chain/src/block_times_cache.rs +++ b/beacon_node/beacon_chain/src/block_times_cache.rs @@ -173,7 +173,7 @@ impl BlockTimesCache { if block_times .timestamps .all_blobs_observed - .map_or(true, |prev| timestamp > prev) + .is_none_or(|prev| timestamp > prev) { block_times.timestamps.all_blobs_observed = Some(timestamp); } @@ -195,7 +195,7 @@ impl BlockTimesCache { .entry(block_root) .or_insert_with(|| BlockTimesCacheValue::new(slot)); let existing_timestamp = field(&mut block_times.timestamps); - if existing_timestamp.map_or(true, |prev| timestamp < prev) { + if existing_timestamp.is_none_or(|prev| timestamp < prev) { *existing_timestamp = Some(timestamp); } } diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 1265276376..70d653524b 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -74,7 +74,6 @@ use metrics::TryExt; use parking_lot::RwLockReadGuard; use proto_array::Block as ProtoBlock; use safe_arith::ArithError; -use slog::{debug, error, Logger}; use slot_clock::SlotClock; use ssz::Encode; use ssz_derive::{Decode, Encode}; @@ -94,6 +93,7 @@ use std::sync::Arc; use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp}; use strum::AsRefStr; use task_executor::JoinHandle; +use tracing::{debug, error}; use types::{ data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError, BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload, @@ -282,6 +282,9 @@ pub enum BlockError { /// problems to worry about than losing peers, and we're doing the network a favour by /// disconnecting. ParentExecutionPayloadInvalid { parent_root: Hash256 }, + /// This is a known invalid block that was listed in Lighthouses configuration. + /// At the moment this error is only relevant as part of the Holesky network recovery efforts. + KnownInvalidExecutionPayload(Hash256), /// The block is a slashable equivocation from the proposer. /// /// ## Peer scoring @@ -680,6 +683,7 @@ pub struct GossipVerifiedBlock { pub block_root: Hash256, parent: Option>, consensus_context: ConsensusContext, + custody_columns_count: usize, } /// A wrapper around a `SignedBeaconBlock` that indicates that all signatures (except the deposit @@ -715,6 +719,7 @@ pub trait IntoGossipVerifiedBlock: Sized { fn into_gossip_verified_block( self, chain: &BeaconChain, + custody_columns_count: usize, ) -> Result, BlockError>; fn inner_block(&self) -> Arc>; } @@ -723,6 +728,7 @@ impl IntoGossipVerifiedBlock for GossipVerifiedBlock fn into_gossip_verified_block( self, _chain: &BeaconChain, + _custody_columns_count: usize, ) -> Result, BlockError> { Ok(self) } @@ -735,8 +741,9 @@ impl IntoGossipVerifiedBlock for Arc, + custody_columns_count: usize, ) -> Result, BlockError> { - GossipVerifiedBlock::new(self, chain) + GossipVerifiedBlock::new(self, chain, custody_columns_count) } fn inner_block(&self) -> Arc> { @@ -805,6 +812,7 @@ impl GossipVerifiedBlock { pub fn new( block: Arc>, chain: &BeaconChain, + custody_columns_count: usize, ) -> Result { // If the block is valid for gossip we don't supply it to the slasher here because // we assume it will be transformed into a fully verified block. We *do* need to supply @@ -814,12 +822,14 @@ impl GossipVerifiedBlock { // The `SignedBeaconBlock` and `SignedBeaconBlockHeader` have the same canonical root, // but it's way quicker to calculate root of the header since the hash of the tree rooted // at `BeaconBlockBody` is already computed in the header. - Self::new_without_slasher_checks(block, &header, chain).map_err(|e| { - process_block_slash_info::<_, BlockError>( - chain, - BlockSlashInfo::from_early_error_block(header, e), - ) - }) + Self::new_without_slasher_checks(block, &header, chain, custody_columns_count).map_err( + |e| { + process_block_slash_info::<_, BlockError>( + chain, + BlockSlashInfo::from_early_error_block(header, e), + ) + }, + ) } /// As for new, but doesn't pass the block to the slasher. @@ -827,6 +837,7 @@ impl GossipVerifiedBlock { block: Arc>, block_header: &SignedBeaconBlockHeader, chain: &BeaconChain, + custody_columns_count: usize, ) -> Result { // Ensure the block is the correct structure for the fork at `block.slot()`. block @@ -862,6 +873,9 @@ impl GossipVerifiedBlock { return Err(BlockError::DuplicateFullyImported(block_root)); } + // Do not process a block that is known to be invalid. + chain.check_invalid_block_roots(block_root)?; + // Do not process a block that doesn't descend from the finalized root. // // We check this *before* we load the parent so that we can return a more detailed error. @@ -924,12 +938,11 @@ impl GossipVerifiedBlock { let (mut parent, block) = load_parent(block, chain)?; debug!( - chain.log, - "Proposer shuffling cache miss"; - "parent_root" => ?parent.beacon_block_root, - "parent_slot" => parent.beacon_block.slot(), - "block_root" => ?block_root, - "block_slot" => block.slot(), + parent_root = ?parent.beacon_block_root, + parent_slot = %parent.beacon_block.slot(), + ?block_root, + block_slot = %block.slot(), + "Proposer shuffling cache miss" ); // The state produced is only valid for determining proposer/attester shuffling indices. @@ -1031,6 +1044,7 @@ impl GossipVerifiedBlock { block_root, parent, consensus_context, + custody_columns_count, }) } @@ -1081,6 +1095,9 @@ impl SignatureVerifiedBlock { .fork_name(&chain.spec) .map_err(BlockError::InconsistentFork)?; + // Check whether the block is a banned block prior to loading the parent. + chain.check_invalid_block_roots(block_root)?; + let (mut parent, block) = load_parent(block, chain)?; let state = cheap_state_advance_to_obtain_committees::<_, BlockError>( @@ -1175,6 +1192,7 @@ impl SignatureVerifiedBlock { block: MaybeAvailableBlock::AvailabilityPending { block_root: from.block_root, block, + custody_columns_count: from.custody_columns_count, }, block_root: from.block_root, parent: Some(parent), @@ -1536,10 +1554,9 @@ impl ExecutionPendingBlock { // Expose Prometheus metrics. if let Err(e) = summary.observe_metrics() { error!( - chain.log, - "Failed to observe epoch summary metrics"; - "src" => "block_verification", - "error" => ?e + src = "block_verification", + error = ?e, + "Failed to observe epoch summary metrics" ); } summaries.push(summary); @@ -1567,9 +1584,8 @@ impl ExecutionPendingBlock { validator_monitor.process_validator_statuses(epoch, summary, &chain.spec) { error!( - chain.log, - "Failed to process validator statuses"; - "error" => ?e + error = ?e, + "Failed to process validator statuses" ); } } @@ -1609,12 +1625,8 @@ impl ExecutionPendingBlock { * invalid. */ - write_state( - &format!("state_pre_block_{}", block_root), - &state, - &chain.log, - ); - write_block(block.as_block(), block_root, &chain.log); + write_state(&format!("state_pre_block_{}", block_root), &state); + write_block(block.as_block(), block_root); let core_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CORE); @@ -1647,11 +1659,7 @@ impl ExecutionPendingBlock { metrics::stop_timer(state_root_timer); - write_state( - &format!("state_post_block_{}", block_root), - &state, - &chain.log, - ); + write_state(&format!("state_post_block_{}", block_root), &state); /* * Check to ensure the state root on the block matches the one we have calculated. @@ -1707,7 +1715,6 @@ impl ExecutionPendingBlock { parent_eth1_finalization_data, confirmed_state_roots, consensus_context, - data_column_recv: None, }, payload_verification_handle, }) @@ -1758,7 +1765,22 @@ pub fn check_block_is_finalized_checkpoint_or_descendant< fork_choice: &BeaconForkChoice, block: B, ) -> Result { - if fork_choice.is_finalized_checkpoint_or_descendant(block.parent_root()) { + // If we have a split block newer than finalization then we also ban blocks which are not + // descended from that split block. It's important not to try checking `is_descendant` if + // finality is ahead of the split and the split block has been pruned, as `is_descendant` will + // return `false` in this case. + let finalized_slot = fork_choice + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()); + let split = chain.store.get_split_info(); + let is_descendant_from_split_block = split.slot == 0 + || split.slot <= finalized_slot + || fork_choice.is_descendant(split.block_root, block.parent_root()); + + if fork_choice.is_finalized_checkpoint_or_descendant(block.parent_root()) + && is_descendant_from_split_block + { Ok(block) } else { // If fork choice does *not* consider the parent to be a descendant of the finalized block, @@ -1943,19 +1965,17 @@ fn load_parent>( if !state.all_caches_built() { debug!( - chain.log, - "Parent state lacks built caches"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), + block_slot = %block.slot(), + state_slot = %state.slot(), + "Parent state lacks built caches" ); } if block.slot() != state.slot() { debug!( - chain.log, - "Parent state is not advanced"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), + block_slot = %block.slot(), + state_slot = %state.slot(), + "Parent state is not advanced" ); } @@ -2161,14 +2181,11 @@ pub fn verify_header_signature( } } -fn write_state(prefix: &str, state: &BeaconState, log: &Logger) { +fn write_state(prefix: &str, state: &BeaconState) { if WRITE_BLOCK_PROCESSING_SSZ { let mut state = state.clone(); let Ok(root) = state.canonical_root() else { - error!( - log, - "Unable to hash state for writing"; - ); + error!("Unable to hash state for writing"); return; }; let filename = format!("{}_slot_{}_root_{}.ssz", prefix, state.slot(), root); @@ -2181,16 +2198,15 @@ fn write_state(prefix: &str, state: &BeaconState, log: &Logger) { let _ = file.write_all(&state.as_ssz_bytes()); } Err(e) => error!( - log, - "Failed to log state"; - "path" => format!("{:?}", path), - "error" => format!("{:?}", e) + ?path, + error = ?e, + "Failed to log state" ), } } } -fn write_block(block: &SignedBeaconBlock, root: Hash256, log: &Logger) { +fn write_block(block: &SignedBeaconBlock, root: Hash256) { if WRITE_BLOCK_PROCESSING_SSZ { let filename = format!("block_slot_{}_root{}.ssz", block.slot(), root); let mut path = std::env::temp_dir().join("lighthouse"); @@ -2202,10 +2218,9 @@ fn write_block(block: &SignedBeaconBlock, root: Hash256, log: &Lo let _ = file.write_all(&block.as_ssz_bytes()); } Err(e) => error!( - log, - "Failed to log block"; - "path" => format!("{:?}", path), - "error" => format!("{:?}", e) + ?path, + error = ?e, + "Failed to log block" ), } } diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index 38d0fc708c..d3a6e93862 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -7,11 +7,10 @@ use derivative::Derivative; use state_processing::ConsensusContext; use std::fmt::{Debug, Formatter}; use std::sync::Arc; -use tokio::sync::oneshot; use types::blob_sidecar::BlobIdentifier; use types::{ - BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, DataColumnSidecarList, - Epoch, EthSpec, Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot, + BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, Epoch, EthSpec, + Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot, }; /// A block that has been received over RPC. It has 2 internal variants: @@ -32,6 +31,7 @@ use types::{ pub struct RpcBlock { block_root: Hash256, block: RpcBlockInner, + custody_columns_count: usize, } impl Debug for RpcBlock { @@ -45,6 +45,10 @@ impl RpcBlock { self.block_root } + pub fn custody_columns_count(&self) -> usize { + self.custody_columns_count + } + pub fn as_block(&self) -> &SignedBeaconBlock { match &self.block { RpcBlockInner::Block(block) => block, @@ -105,6 +109,8 @@ impl RpcBlock { Self { block_root, block: RpcBlockInner::Block(block), + // Block has zero columns + custody_columns_count: 0, } } @@ -146,6 +152,8 @@ impl RpcBlock { Ok(Self { block_root, block: inner, + // Block is before PeerDAS + custody_columns_count: 0, }) } @@ -153,6 +161,7 @@ impl RpcBlock { block_root: Option, block: Arc>, custody_columns: Vec>, + custody_columns_count: usize, spec: &ChainSpec, ) -> Result { let block_root = block_root.unwrap_or_else(|| get_block_root(&block)); @@ -173,6 +182,7 @@ impl RpcBlock { Ok(Self { block_root, block: inner, + custody_columns_count, }) } @@ -240,10 +250,12 @@ impl ExecutedBlock { MaybeAvailableBlock::AvailabilityPending { block_root: _, block: pending_block, + custody_columns_count, } => Self::AvailabilityPending(AvailabilityPendingExecutedBlock::new( pending_block, import_data, payload_verification_outcome, + custody_columns_count, )), } } @@ -265,7 +277,6 @@ impl ExecutedBlock { /// A block that has completed all pre-deneb block processing checks including verification /// by an EL client **and** has all requisite blob data to be imported into fork choice. -#[derive(PartialEq)] pub struct AvailableExecutedBlock { pub block: AvailableBlock, pub import_data: BlockImportData, @@ -310,6 +321,7 @@ pub struct AvailabilityPendingExecutedBlock { pub block: Arc>, pub import_data: BlockImportData, pub payload_verification_outcome: PayloadVerificationOutcome, + pub custody_columns_count: usize, } impl AvailabilityPendingExecutedBlock { @@ -317,11 +329,13 @@ impl AvailabilityPendingExecutedBlock { block: Arc>, import_data: BlockImportData, payload_verification_outcome: PayloadVerificationOutcome, + custody_columns_count: usize, ) -> Self { Self { block, import_data, payload_verification_outcome, + custody_columns_count, } } @@ -338,8 +352,7 @@ impl AvailabilityPendingExecutedBlock { } } -#[derive(Debug, Derivative)] -#[derivative(PartialEq)] +#[derive(Debug, PartialEq)] pub struct BlockImportData { pub block_root: Hash256, pub state: BeaconState, @@ -347,12 +360,6 @@ pub struct BlockImportData { pub parent_eth1_finalization_data: Eth1FinalizationData, pub confirmed_state_roots: Vec, pub consensus_context: ConsensusContext, - #[derivative(PartialEq = "ignore")] - /// An optional receiver for `DataColumnSidecarList`. - /// - /// This field is `Some` when data columns are being computed asynchronously. - /// The resulting `DataColumnSidecarList` will be sent through this receiver. - pub data_column_recv: Option>>, } impl BlockImportData { @@ -371,7 +378,6 @@ impl BlockImportData { }, confirmed_state_roots: vec![], consensus_context: ConsensusContext::new(Slot::new(0)), - data_column_recv: None, } } } @@ -449,19 +455,13 @@ impl AsBlock for MaybeAvailableBlock { fn as_block(&self) -> &SignedBeaconBlock { match &self { MaybeAvailableBlock::Available(block) => block.as_block(), - MaybeAvailableBlock::AvailabilityPending { - block_root: _, - block, - } => block, + MaybeAvailableBlock::AvailabilityPending { block, .. } => block, } } fn block_cloned(&self) -> Arc> { match &self { MaybeAvailableBlock::Available(block) => block.block_cloned(), - MaybeAvailableBlock::AvailabilityPending { - block_root: _, - block, - } => block.clone(), + MaybeAvailableBlock::AvailabilityPending { block, .. } => block.clone(), } } fn canonical_root(&self) -> Hash256 { diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 8d62478bea..f6d18c3705 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -27,11 +27,11 @@ use execution_layer::ExecutionLayer; use fork_choice::{ForkChoice, ResetPayloadStatuses}; use futures::channel::mpsc::Sender; use kzg::Kzg; +use logging::crit; use operation_pool::{OperationPool, PersistedOperationPool}; use parking_lot::{Mutex, RwLock}; use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use slasher::Slasher; -use slog::{crit, debug, error, info, o, Logger}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::{per_slot_processing, AllCaches}; use std::marker::PhantomData; @@ -39,6 +39,7 @@ use std::sync::Arc; use std::time::Duration; use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; +use tracing::{debug, error, info}; use types::{ BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, Epoch, EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot, @@ -96,7 +97,6 @@ pub struct BeaconChainBuilder { validator_pubkey_cache: Option>, spec: Arc, chain_config: ChainConfig, - log: Option, beacon_graffiti: GraffitiOrigin, slasher: Option>>, // Pending I/O batch that is constructed during building and should be executed atomically @@ -140,7 +140,6 @@ where validator_pubkey_cache: None, spec: Arc::new(E::default_spec()), chain_config: ChainConfig::default(), - log: None, beacon_graffiti: GraffitiOrigin::default(), slasher: None, pending_io_batch: vec![], @@ -218,14 +217,6 @@ where self } - /// Sets the logger. - /// - /// Should generally be called early in the build chain. - pub fn logger(mut self, log: Logger) -> Self { - self.log = Some(log); - self - } - /// Sets the task executor. pub fn task_executor(mut self, task_executor: TaskExecutor) -> Self { self.task_executor = Some(task_executor); @@ -261,13 +252,7 @@ where /// /// May initialize several components; including the op_pool and finalized checkpoints. pub fn resume_from_db(mut self) -> Result { - let log = self.log.as_ref().ok_or("resume_from_db requires a log")?; - - info!( - log, - "Starting beacon chain"; - "method" => "resume" - ); + info!(method = "resume", "Starting beacon chain"); let store = self .store @@ -289,7 +274,6 @@ where self.chain_config.always_reset_payload_statuses, ), &self.spec, - log, ) .map_err(|e| format!("Unable to load fork choice from disk: {:?}", e))? .ok_or("Fork choice not found in store")?; @@ -298,8 +282,13 @@ where .get_blinded_block(&chain.genesis_block_root) .map_err(|e| descriptive_db_error("genesis block", &e))? .ok_or("Genesis block not found in store")?; + // We're resuming from some state in the db so it makes sense to cache it. let genesis_state = store - .get_state(&genesis_block.state_root(), Some(genesis_block.slot())) + .get_state( + &genesis_block.state_root(), + Some(genesis_block.slot()), + true, + ) .map_err(|e| descriptive_db_error("genesis state", &e))? .ok_or("Genesis state not found in store")?; @@ -451,19 +440,14 @@ where .store .clone() .ok_or("weak_subjectivity_state requires a store")?; - let log = self - .log - .as_ref() - .ok_or("weak_subjectivity_state requires a log")?; // Ensure the state is advanced to an epoch boundary. let slots_per_epoch = E::slots_per_epoch(); if weak_subj_state.slot() % slots_per_epoch != 0 { debug!( - log, - "Advancing checkpoint state to boundary"; - "state_slot" => weak_subj_state.slot(), - "block_slot" => weak_subj_block.slot(), + state_slot = %weak_subj_state.slot(), + block_slot = %weak_subj_block.slot(), + "Advancing checkpoint state to boundary" ); while weak_subj_state.slot() % slots_per_epoch != 0 { per_slot_processing(&mut weak_subj_state, None, &self.spec) @@ -731,7 +715,6 @@ where mut self, ) -> Result>, String> { - let log = self.log.ok_or("Cannot build without a logger")?; let slot_clock = self .slot_clock .ok_or("Cannot build without a slot_clock.")?; @@ -749,11 +732,8 @@ where let head_tracker = Arc::new(self.head_tracker.unwrap_or_default()); let beacon_proposer_cache: Arc> = <_>::default(); - let mut validator_monitor = ValidatorMonitor::new( - validator_monitor_config, - beacon_proposer_cache.clone(), - log.new(o!("service" => "val_mon")), - ); + let mut validator_monitor = + ValidatorMonitor::new(validator_monitor_config, beacon_proposer_cache.clone()); let current_slot = if slot_clock .is_prior_to_genesis() @@ -776,19 +756,17 @@ where Ok(None) => return Err("Head block not found in store".into()), Err(StoreError::SszDecodeError(_)) => { error!( - log, - "Error decoding head block"; - "message" => "This node has likely missed a hard fork. \ - It will try to revert the invalid blocks and keep running, \ - but any stray blocks and states will not be deleted. \ - Long-term you should consider re-syncing this node." + message = "This node has likely missed a hard fork. \ + It will try to revert the invalid blocks and keep running, \ + but any stray blocks and states will not be deleted. \ + Long-term you should consider re-syncing this node.", + "Error decoding head block" ); let (block_root, block) = revert_to_fork_boundary( current_slot, initial_head_block_root, store.clone(), &self.spec, - &log, )?; // Update head tracker. @@ -842,18 +820,34 @@ where )); } - let validator_pubkey_cache = self.validator_pubkey_cache.map(Ok).unwrap_or_else(|| { - ValidatorPubkeyCache::new(&head_snapshot.beacon_state, store.clone()) - .map_err(|e| format!("Unable to init validator pubkey cache: {:?}", e)) - })?; + let validator_pubkey_cache = self + .validator_pubkey_cache + .map(|mut validator_pubkey_cache| { + // If any validators weren't persisted to disk on previous runs, this will use the head state to + // "top-up" the in-memory validator cache and its on-disk representation with any missing validators. + let pubkey_store_ops = validator_pubkey_cache + .import_new_pubkeys(&head_snapshot.beacon_state) + .map_err(|e| format!("Unable to top-up persisted pubkey cache {:?}", e))?; + if !pubkey_store_ops.is_empty() { + // Write any missed validators to disk + debug!( + missing_validators = pubkey_store_ops.len(), + "Topping up validator pubkey cache" + ); + store + .do_atomically_with_block_and_blobs_cache(pubkey_store_ops) + .map_err(|e| format!("Unable to write pubkeys to disk {:?}", e))?; + } + Ok(validator_pubkey_cache) + }) + .unwrap_or_else(|| { + ValidatorPubkeyCache::new(&head_snapshot.beacon_state, store.clone()) + .map_err(|e| format!("Unable to init validator pubkey cache: {:?}", e)) + })?; let migrator_config = self.store_migrator_config.unwrap_or_default(); - let store_migrator = BackgroundMigrator::new( - store.clone(), - migrator_config, - genesis_block_root, - log.clone(), - ); + let store_migrator = + BackgroundMigrator::new(store.clone(), migrator_config, genesis_block_root); if let Some(slot) = slot_clock.now() { validator_monitor.process_valid_state( @@ -978,9 +972,8 @@ where shuffling_cache: RwLock::new(ShufflingCache::new( shuffling_cache_size, head_shuffling_ids, - log.clone(), )), - eth1_finalization_cache: RwLock::new(Eth1FinalizationCache::new(log.clone())), + eth1_finalization_cache: RwLock::new(Eth1FinalizationCache::default()), beacon_proposer_cache, block_times_cache: <_>::default(), pre_finalization_block_cache: <_>::default(), @@ -993,26 +986,17 @@ where shutdown_sender: self .shutdown_sender .ok_or("Cannot build without a shutdown sender.")?, - log: log.clone(), graffiti_calculator: GraffitiCalculator::new( self.beacon_graffiti, self.execution_layer, slot_clock.slot_duration() * E::slots_per_epoch() as u32, - log.clone(), ), slasher: self.slasher.clone(), validator_monitor: RwLock::new(validator_monitor), genesis_backfill_slot, data_availability_checker: Arc::new( - DataAvailabilityChecker::new( - slot_clock, - self.kzg.clone(), - store, - self.import_all_data_columns, - self.spec, - log.new(o!("service" => "data_availability_checker")), - ) - .map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?, + DataAvailabilityChecker::new(slot_clock, self.kzg.clone(), store, self.spec) + .map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?, ), kzg: self.kzg.clone(), }; @@ -1037,25 +1021,23 @@ where &head.beacon_state, ) { crit!( - log, - "Weak subjectivity checkpoint verification failed on startup!"; - "head_block_root" => format!("{}", head.beacon_block_root), - "head_slot" => format!("{}", head.beacon_block.slot()), - "finalized_epoch" => format!("{}", head.beacon_state.finalized_checkpoint().epoch), - "wss_checkpoint_epoch" => format!("{}", wss_checkpoint.epoch), - "error" => format!("{:?}", e), + head_block_root = %head.beacon_block_root, + head_slot = %head.beacon_block.slot(), + finalized_epoch = %head.beacon_state.finalized_checkpoint().epoch, + wss_checkpoint_epoch = %wss_checkpoint.epoch, + error = ?e, + "Weak subjectivity checkpoint verification failed on startup!" ); - crit!(log, "You must use the `--purge-db` flag to clear the database and restart sync. You may be on a hostile network."); + crit!("You must use the `--purge-db` flag to clear the database and restart sync. You may be on a hostile network."); return Err(format!("Weak subjectivity verification failed: {:?}", e)); } } info!( - log, - "Beacon chain initialized"; - "head_state" => format!("{}", head.beacon_state_root()), - "head_block" => format!("{}", head.beacon_block_root), - "head_slot" => format!("{}", head.beacon_block.slot()), + head_state = %head.beacon_state_root(), + head_block = %head.beacon_block_root, + head_slot = %head.beacon_block.slot(), + "Beacon chain initialized" ); // Check for states to reconstruct (in the background). @@ -1068,11 +1050,10 @@ where // Prune finalized execution payloads in the background. if beacon_chain.store.get_config().prune_payloads { let store = beacon_chain.store.clone(); - let log = log.clone(); beacon_chain.task_executor.spawn_blocking( move || { if let Err(e) = store.try_prune_execution_payloads(false) { - error!(log, "Error pruning payloads in background"; "error" => ?e); + error!( error = ?e,"Error pruning payloads in background"); } }, "prune_payloads_background", @@ -1105,13 +1086,7 @@ where /// Sets the `BeaconChain` eth1 back-end to produce predictably junk data when producing blocks. pub fn dummy_eth1_backend(mut self) -> Result { - let log = self - .log - .as_ref() - .ok_or("dummy_eth1_backend requires a log")?; - - let backend = - CachingEth1Backend::new(Eth1Config::default(), log.clone(), self.spec.clone())?; + let backend = CachingEth1Backend::new(Eth1Config::default(), self.spec.clone())?; self.eth1_chain = Some(Eth1Chain::new_dummy(backend)); @@ -1186,7 +1161,6 @@ mod test { use genesis::{ generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH, }; - use sloggers::{null::NullLoggerBuilder, Build}; use ssz::Encode; use std::time::Duration; use store::config::StoreConfig; @@ -1197,27 +1171,16 @@ mod test { type TestEthSpec = MinimalEthSpec; type Builder = BeaconChainBuilder>; - fn get_logger() -> Logger { - let builder = NullLoggerBuilder; - builder.build().expect("should build logger") - } - #[test] fn recent_genesis() { let validator_count = 1; let genesis_time = 13_371_337; - let log = get_logger(); let store: HotColdDB< MinimalEthSpec, MemoryStore, MemoryStore, - > = HotColdDB::open_ephemeral( - StoreConfig::default(), - ChainSpec::minimal().into(), - log.clone(), - ) - .unwrap(); + > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into()).unwrap(); let spec = MinimalEthSpec::default_spec(); let genesis_state = interop_genesis_state( @@ -1235,7 +1198,6 @@ mod test { let kzg = get_kzg(&spec); let chain = Builder::new(MinimalEthSpec, kzg) - .logger(log.clone()) .store(Arc::new(store)) .task_executor(runtime.task_executor.clone()) .genesis_state(genesis_state) diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 4e21372efb..d99c6038d3 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -47,14 +47,15 @@ use fork_choice::{ ResetPayloadStatuses, }; use itertools::process_results; +use logging::crit; use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use state_processing::AllCaches; use std::sync::Arc; use std::time::Duration; use store::{iter::StateRootsIterator, KeyValueStoreOp, StoreItem}; use task_executor::{JoinHandle, ShutdownReason}; +use tracing::{debug, error, info, warn}; use types::*; /// Simple wrapper around `RwLock` that uses private visibility to prevent any other modules from @@ -286,10 +287,9 @@ impl CanonicalHead { reset_payload_statuses: ResetPayloadStatuses, store: &BeaconStore, spec: &ChainSpec, - log: &Logger, ) -> Result<(), Error> { let fork_choice = - >::load_fork_choice(store.clone(), reset_payload_statuses, spec, log)? + >::load_fork_choice(store.clone(), reset_payload_statuses, spec)? .ok_or(Error::MissingPersistedForkChoice)?; let fork_choice_view = fork_choice.cached_fork_choice_view(); let beacon_block_root = fork_choice_view.head_block_root; @@ -475,9 +475,8 @@ impl BeaconChain { match self.slot() { Ok(current_slot) => self.recompute_head_at_slot(current_slot).await, Err(e) => error!( - self.log, - "No slot when recomputing head"; - "error" => ?e + error = ?e, + "No slot when recomputing head" ), } } @@ -515,18 +514,13 @@ impl BeaconChain { Ok(Some(())) => (), // The async task did not complete successfully since the runtime is shutting down. Ok(None) => { - debug!( - self.log, - "Did not update EL fork choice"; - "info" => "shutting down" - ); + debug!(info = "shutting down", "Did not update EL fork choice"); } // The async task did not complete successfully, tokio returned an error. Err(e) => { error!( - self.log, - "Did not update EL fork choice"; - "error" => ?e + error = ?e, + "Did not update EL fork choice" ); } }, @@ -534,17 +528,15 @@ impl BeaconChain { Ok(Err(e)) => { metrics::inc_counter(&metrics::FORK_CHOICE_ERRORS); error!( - self.log, - "Error whist recomputing head"; - "error" => ?e + error = ?e, + "Error whist recomputing head" ); } // There was an error spawning the task. Err(e) => { error!( - self.log, - "Failed to spawn recompute head task"; - "error" => ?e + error = ?e, + "Failed to spawn recompute head task" ); } } @@ -627,9 +619,8 @@ impl BeaconChain { // nothing to do. if new_view == old_view { debug!( - self.log, - "No change in canonical head"; - "head" => ?new_view.head_block_root + head = ?new_view.head_block_root, + "No change in canonical head" ); return Ok(None); } @@ -639,7 +630,7 @@ impl BeaconChain { let new_forkchoice_update_parameters = fork_choice_read_lock.get_forkchoice_update_parameters(); - perform_debug_logging::(&old_view, &new_view, &fork_choice_read_lock, &self.log); + perform_debug_logging::(&old_view, &new_view, &fork_choice_read_lock); // Drop the read lock, it's no longer required and holding it any longer than necessary // will just cause lock contention. @@ -732,9 +723,8 @@ impl BeaconChain { self.after_new_head(&old_cached_head, &new_cached_head, new_head_proto_block) { crit!( - self.log, - "Error updating canonical head"; - "error" => ?e + error = ?e, + "Error updating canonical head" ); } } @@ -751,9 +741,8 @@ impl BeaconChain { self.after_finalization(&new_cached_head, new_view, finalized_proto_block) { crit!( - self.log, - "Error updating finalization"; - "error" => ?e + error = ?e, + "Error updating finalization" ); } } @@ -784,6 +773,12 @@ impl BeaconChain { .execution_status .is_optimistic_or_invalid(); + // Update the state cache so it doesn't mistakenly prune the new head. + self.store + .state_cache + .lock() + .update_head_block_root(new_cached_head.head_block_root()); + // Detect and potentially report any re-orgs. let reorg_distance = detect_reorg( &old_snapshot.beacon_state, @@ -791,7 +786,6 @@ impl BeaconChain { &new_snapshot.beacon_state, new_snapshot.beacon_block_root, &self.spec, - &self.log, ); // Determine if the new head is in a later epoch to the previous head. @@ -824,10 +818,9 @@ impl BeaconChain { .update_head_shuffling_ids(head_shuffling_ids), Err(e) => { error!( - self.log, - "Failed to get head shuffling ids"; - "error" => ?e, - "head_block_root" => ?new_snapshot.beacon_block_root + error = ?e, + head_block_root = ?new_snapshot.beacon_block_root, + "Failed to get head shuffling ids" ); } } @@ -844,7 +837,6 @@ impl BeaconChain { .as_utf8_lossy(), &self.slot_clock, self.event_handler.as_ref(), - &self.log, ); if is_epoch_transition || reorg_distance.is_some() { @@ -872,9 +864,8 @@ impl BeaconChain { } (Err(e), _) | (_, Err(e)) => { warn!( - self.log, - "Unable to find dependent roots, cannot register head event"; - "error" => ?e + error = ?e, + "Unable to find dependent roots, cannot register head event" ); } } @@ -1037,11 +1028,10 @@ fn check_finalized_payload_validity( ) -> Result<(), Error> { if let ExecutionStatus::Invalid(block_hash) = finalized_proto_block.execution_status { crit!( - chain.log, - "Finalized block has an invalid payload"; - "msg" => "You must use the `--purge-db` flag to clear the database and restart sync. \ + ?block_hash, + msg = "You must use the `--purge-db` flag to clear the database and restart sync. \ You may be on a hostile network.", - "block_hash" => ?block_hash + "Finalized block has an invalid payload" ); let mut shutdown_sender = chain.shutdown_sender(); shutdown_sender @@ -1083,38 +1073,34 @@ fn perform_debug_logging( old_view: &ForkChoiceView, new_view: &ForkChoiceView, fork_choice: &BeaconForkChoice, - log: &Logger, ) { if new_view.head_block_root != old_view.head_block_root { debug!( - log, - "Fork choice updated head"; - "new_head_weight" => ?fork_choice - .get_block_weight(&new_view.head_block_root), - "new_head" => ?new_view.head_block_root, - "old_head_weight" => ?fork_choice - .get_block_weight(&old_view.head_block_root), - "old_head" => ?old_view.head_block_root, + new_head_weight = ?fork_choice + .get_block_weight(&new_view.head_block_root), + new_head = ?new_view.head_block_root, + old_head_weight = ?fork_choice + .get_block_weight(&old_view.head_block_root), + old_head = ?old_view.head_block_root, + "Fork choice updated head" ) } if new_view.justified_checkpoint != old_view.justified_checkpoint { debug!( - log, - "Fork choice justified"; - "new_root" => ?new_view.justified_checkpoint.root, - "new_epoch" => new_view.justified_checkpoint.epoch, - "old_root" => ?old_view.justified_checkpoint.root, - "old_epoch" => old_view.justified_checkpoint.epoch, + new_root = ?new_view.justified_checkpoint.root, + new_epoch = %new_view.justified_checkpoint.epoch, + old_root = ?old_view.justified_checkpoint.root, + old_epoch = %old_view.justified_checkpoint.epoch, + "Fork choice justified" ) } if new_view.finalized_checkpoint != old_view.finalized_checkpoint { debug!( - log, - "Fork choice finalized"; - "new_root" => ?new_view.finalized_checkpoint.root, - "new_epoch" => new_view.finalized_checkpoint.epoch, - "old_root" => ?old_view.finalized_checkpoint.root, - "old_epoch" => old_view.finalized_checkpoint.epoch, + new_root = ?new_view.finalized_checkpoint.root, + new_epoch = %new_view.finalized_checkpoint.epoch, + old_root = ?old_view.finalized_checkpoint.root, + old_epoch = %old_view.finalized_checkpoint.epoch, + "Fork choice finalized" ) } } @@ -1149,9 +1135,8 @@ fn spawn_execution_layer_updates( .await { crit!( - chain.log, - "Failed to update execution head"; - "error" => ?e + error = ?e, + "Failed to update execution head" ); } @@ -1165,9 +1150,8 @@ fn spawn_execution_layer_updates( // know. if let Err(e) = chain.prepare_beacon_proposer(current_slot).await { crit!( - chain.log, - "Failed to prepare proposers after fork choice"; - "error" => ?e + error = ?e, + "Failed to prepare proposers after fork choice" ); } }, @@ -1188,7 +1172,6 @@ fn detect_reorg( new_state: &BeaconState, new_block_root: Hash256, spec: &ChainSpec, - log: &Logger, ) -> Option { let is_reorg = new_state .get_block_root(old_state.slot()) @@ -1199,11 +1182,7 @@ fn detect_reorg( match find_reorg_slot(old_state, old_block_root, new_state, new_block_root, spec) { Ok(slot) => old_state.slot().saturating_sub(slot), Err(e) => { - warn!( - log, - "Could not find re-org depth"; - "error" => format!("{:?}", e), - ); + warn!(error = ?e, "Could not find re-org depth"); return None; } }; @@ -1215,13 +1194,12 @@ fn detect_reorg( reorg_distance.as_u64() as i64, ); info!( - log, - "Beacon chain re-org"; - "previous_head" => ?old_block_root, - "previous_slot" => old_state.slot(), - "new_head" => ?new_block_root, - "new_slot" => new_state.slot(), - "reorg_distance" => reorg_distance, + previous_head = ?old_block_root, + previous_slot = %old_state.slot(), + new_head = ?new_block_root, + new_slot = %new_state.slot(), + %reorg_distance, + "Beacon chain re-org" ); Some(reorg_distance) @@ -1301,7 +1279,6 @@ fn observe_head_block_delays( head_block_graffiti: String, slot_clock: &S, event_handler: Option<&ServerSentEventHandler>, - log: &Logger, ) { let block_time_set_as_head = timestamp_now(); let head_block_root = head_block.root; @@ -1434,37 +1411,35 @@ fn observe_head_block_delays( if late_head { metrics::inc_counter(&metrics::BEACON_BLOCK_DELAY_HEAD_SLOT_START_EXCEEDED_TOTAL); debug!( - log, - "Delayed head block"; - "block_root" => ?head_block_root, - "proposer_index" => head_block_proposer_index, - "slot" => head_block_slot, - "total_delay_ms" => block_delay_total.as_millis(), - "observed_delay_ms" => format_delay(&block_delays.observed), - "blob_delay_ms" => format_delay(&block_delays.all_blobs_observed), - "consensus_time_ms" => format_delay(&block_delays.consensus_verification_time), - "execution_time_ms" => format_delay(&block_delays.execution_time), - "available_delay_ms" => format_delay(&block_delays.available), - "attestable_delay_ms" => format_delay(&block_delays.attestable), - "imported_time_ms" => format_delay(&block_delays.imported), - "set_as_head_time_ms" => format_delay(&block_delays.set_as_head), + block_root = ?head_block_root, + proposer_index = head_block_proposer_index, + slot = %head_block_slot, + total_delay_ms = block_delay_total.as_millis(), + observed_delay_ms = format_delay(&block_delays.observed), + blob_delay_ms = format_delay(&block_delays.all_blobs_observed), + consensus_time_ms = format_delay(&block_delays.consensus_verification_time), + execution_time_ms = format_delay(&block_delays.execution_time), + available_delay_ms = format_delay(&block_delays.available), + attestable_delay_ms = format_delay(&block_delays.attestable), + imported_time_ms = format_delay(&block_delays.imported), + set_as_head_time_ms = format_delay(&block_delays.set_as_head), + "Delayed head block" ); } else { debug!( - log, - "On-time head block"; - "block_root" => ?head_block_root, - "proposer_index" => head_block_proposer_index, - "slot" => head_block_slot, - "total_delay_ms" => block_delay_total.as_millis(), - "observed_delay_ms" => format_delay(&block_delays.observed), - "blob_delay_ms" => format_delay(&block_delays.all_blobs_observed), - "consensus_time_ms" => format_delay(&block_delays.consensus_verification_time), - "execution_time_ms" => format_delay(&block_delays.execution_time), - "available_delay_ms" => format_delay(&block_delays.available), - "attestable_delay_ms" => format_delay(&block_delays.attestable), - "imported_time_ms" => format_delay(&block_delays.imported), - "set_as_head_time_ms" => format_delay(&block_delays.set_as_head), + block_root = ?head_block_root, + proposer_index = head_block_proposer_index, + slot = %head_block_slot, + total_delay_ms = block_delay_total.as_millis(), + observed_delay_ms = format_delay(&block_delays.observed), + blob_delay_ms = format_delay(&block_delays.all_blobs_observed), + consensus_time_ms = format_delay(&block_delays.consensus_verification_time), + execution_time_ms = format_delay(&block_delays.execution_time), + available_delay_ms = format_delay(&block_delays.available), + attestable_delay_ms = format_delay(&block_delays.attestable), + imported_time_ms = format_delay(&block_delays.imported), + set_as_head_time_ms = format_delay(&block_delays.set_as_head), + "On-time head block" ); } } diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index fcdd57abbc..808c96d965 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -1,7 +1,8 @@ pub use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use serde::{Deserialize, Serialize}; -use std::time::Duration; -use types::{Checkpoint, Epoch}; +use std::str::FromStr; +use std::{collections::HashSet, sync::LazyLock, time::Duration}; +use types::{Checkpoint, Epoch, Hash256}; pub const DEFAULT_RE_ORG_HEAD_THRESHOLD: ReOrgThreshold = ReOrgThreshold(20); pub const DEFAULT_RE_ORG_PARENT_THRESHOLD: ReOrgThreshold = ReOrgThreshold(160); @@ -16,6 +17,15 @@ pub const DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR: u32 = 3; /// Fraction of a slot lookahead for fork choice in the state advance timer (500ms on mainnet). pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24; +/// Default sync tolerance epochs. +pub const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 2; + +/// Invalid block root to be banned from processing and importing on Holesky network by default. +pub static INVALID_HOLESKY_BLOCK_ROOT: LazyLock = LazyLock::new(|| { + Hash256::from_str("2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359") + .expect("valid block root") +}); + #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct ChainConfig { /// Maximum number of slots to skip when importing an attestation. @@ -94,6 +104,18 @@ pub struct ChainConfig { /// The delay in milliseconds applied by the node between sending each blob or data column batch. /// This doesn't apply if the node is the block proposer. pub blob_publication_batch_interval: Duration, + /// The max distance between the head block and the current slot at which Lighthouse will + /// consider itself synced and still serve validator-related requests. + pub sync_tolerance_epochs: u64, + /// Artificial delay for block publishing. For PeerDAS testing only. + pub block_publishing_delay: Option, + /// Artificial delay for data column publishing. For PeerDAS testing only. + pub data_column_publishing_delay: Option, + /// Block roots of "banned" blocks which Lighthouse will refuse to import. + /// + /// On Holesky there is a block which is added to this set by default but which can be removed + /// by using `--invalid-block-roots ""`. + pub invalid_block_roots: HashSet, } impl Default for ChainConfig { @@ -129,6 +151,10 @@ impl Default for ChainConfig { enable_sampling: false, blob_publication_batches: 4, blob_publication_batch_interval: Duration::from_millis(300), + sync_tolerance_epochs: DEFAULT_SYNC_TOLERANCE_EPOCHS, + block_publishing_delay: None, + data_column_publishing_delay: None, + invalid_block_roots: HashSet::new(), } } } diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index f10d59ca1a..2b7ae9e4d1 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -7,7 +7,6 @@ use crate::data_availability_checker::overflow_lru_cache::{ }; use crate::{metrics, BeaconChain, BeaconChainTypes, BeaconStore}; use kzg::Kzg; -use slog::{debug, error, Logger}; use slot_clock::SlotClock; use std::fmt; use std::fmt::Debug; @@ -16,6 +15,7 @@ use std::sync::Arc; use std::time::Duration; use task_executor::TaskExecutor; use tokio::sync::oneshot; +use tracing::{debug, error, info_span, Instrument}; use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList}; use types::{ BlobSidecarList, ChainSpec, DataColumnIdentifier, DataColumnSidecar, DataColumnSidecarList, @@ -75,7 +75,6 @@ pub struct DataAvailabilityChecker { slot_clock: T::SlotClock, kzg: Arc, spec: Arc, - log: Logger, } pub type AvailabilityAndReconstructedColumns = (Availability, DataColumnSidecarList); @@ -91,7 +90,6 @@ pub enum DataColumnReconstructionResult { /// /// Indicates if the block is fully `Available` or if we need blobs or blocks /// to "complete" the requirements for an `AvailableBlock`. -#[derive(PartialEq)] pub enum Availability { MissingComponents(Hash256), Available(Box>), @@ -113,39 +111,17 @@ impl DataAvailabilityChecker { slot_clock: T::SlotClock, kzg: Arc, store: BeaconStore, - import_all_data_columns: bool, spec: Arc, - log: Logger, ) -> Result { - let custody_group_count = spec.custody_group_count(import_all_data_columns); - // This should only panic if the chain spec contains invalid values. - let sampling_size = spec - .sampling_size(custody_group_count) - .expect("should compute node sampling size from valid chain spec"); - - let inner = DataAvailabilityCheckerInner::new( - OVERFLOW_LRU_CAPACITY, - store, - sampling_size as usize, - spec.clone(), - )?; + let inner = DataAvailabilityCheckerInner::new(OVERFLOW_LRU_CAPACITY, store, spec.clone())?; Ok(Self { availability_cache: Arc::new(inner), slot_clock, kzg, spec, - log, }) } - pub fn get_sampling_column_count(&self) -> usize { - self.availability_cache.sampling_column_count() - } - - pub(crate) fn is_supernode(&self) -> bool { - self.get_sampling_column_count() == self.spec.number_of_columns as usize - } - /// Checks if the block root is currenlty in the availability cache awaiting import because /// of missing components. pub fn get_execution_valid_block( @@ -219,7 +195,7 @@ impl DataAvailabilityChecker { .map_err(AvailabilityCheckError::InvalidBlobs)?; self.availability_cache - .put_kzg_verified_blobs(block_root, verified_blobs, None, &self.log) + .put_kzg_verified_blobs(block_root, verified_blobs) } /// Put a list of custody columns received via RPC into the availability cache. This performs KZG @@ -239,11 +215,8 @@ impl DataAvailabilityChecker { .map(KzgVerifiedCustodyDataColumn::from_asserted_custody) .collect::>(); - self.availability_cache.put_kzg_verified_data_columns( - block_root, - verified_custody_columns, - &self.log, - ) + self.availability_cache + .put_kzg_verified_data_columns(block_root, verified_custody_columns) } /// Put a list of blobs received from the EL pool into the availability cache. @@ -253,23 +226,27 @@ impl DataAvailabilityChecker { pub fn put_engine_blobs( &self, block_root: Hash256, + block_epoch: Epoch, blobs: FixedBlobSidecarList, - data_column_recv: Option>>, + data_columns_recv: Option>>, ) -> Result, AvailabilityCheckError> { - let seen_timestamp = self - .slot_clock - .now_duration() - .ok_or(AvailabilityCheckError::SlotClockError)?; - - let verified_blobs = - KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp); - - self.availability_cache.put_kzg_verified_blobs( - block_root, - verified_blobs, - data_column_recv, - &self.log, - ) + // `data_columns_recv` is always Some if block_root is post-PeerDAS + if let Some(data_columns_recv) = data_columns_recv { + self.availability_cache.put_computed_data_columns_recv( + block_root, + block_epoch, + data_columns_recv, + ) + } else { + let seen_timestamp = self + .slot_clock + .now_duration() + .ok_or(AvailabilityCheckError::SlotClockError)?; + self.availability_cache.put_kzg_verified_blobs( + block_root, + KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp), + ) + } } /// Check if we've cached other blobs for this block. If it completes a set and we also @@ -281,12 +258,8 @@ impl DataAvailabilityChecker { &self, gossip_blob: GossipVerifiedBlob, ) -> Result, AvailabilityCheckError> { - self.availability_cache.put_kzg_verified_blobs( - gossip_blob.block_root(), - vec![gossip_blob.into_inner()], - None, - &self.log, - ) + self.availability_cache + .put_kzg_verified_blobs(gossip_blob.block_root(), vec![gossip_blob.into_inner()]) } /// Check if we've cached other data columns for this block. If it satisfies the custody requirement and we also @@ -305,11 +278,8 @@ impl DataAvailabilityChecker { .map(|c| KzgVerifiedCustodyDataColumn::from_asserted_custody(c.into_inner())) .collect::>(); - self.availability_cache.put_kzg_verified_data_columns( - block_root, - custody_columns, - &self.log, - ) + self.availability_cache + .put_kzg_verified_data_columns(block_root, custody_columns) } /// Check if we have all the blobs for a block. Returns `Availability` which has information @@ -319,7 +289,7 @@ impl DataAvailabilityChecker { executed_block: AvailabilityPendingExecutedBlock, ) -> Result, AvailabilityCheckError> { self.availability_cache - .put_pending_executed_block(executed_block, &self.log) + .put_pending_executed_block(executed_block) } pub fn remove_pending_components(&self, block_root: Hash256) { @@ -336,21 +306,25 @@ impl DataAvailabilityChecker { &self, block: RpcBlock, ) -> Result, AvailabilityCheckError> { + let custody_columns_count = block.custody_columns_count(); let (block_root, block, blobs, data_columns) = block.deconstruct(); if self.blobs_required_for_block(&block) { - return if let Some(blob_list) = blobs.as_ref() { + return if let Some(blob_list) = blobs { verify_kzg_for_blob_list(blob_list.iter(), &self.kzg) .map_err(AvailabilityCheckError::InvalidBlobs)?; Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs, + blob_data: AvailableBlockData::Blobs(blob_list), blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), })) } else { - Ok(MaybeAvailableBlock::AvailabilityPending { block_root, block }) + Ok(MaybeAvailableBlock::AvailabilityPending { + block_root, + block, + custody_columns_count, + }) }; } if self.data_columns_required_for_block(&block) { @@ -365,27 +339,29 @@ impl DataAvailabilityChecker { Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - blobs_available_timestamp: None, - data_columns: Some( + blob_data: AvailableBlockData::DataColumns( data_column_list .into_iter() .map(|d| d.clone_arc()) .collect(), ), + blobs_available_timestamp: None, spec: self.spec.clone(), })) } else { - Ok(MaybeAvailableBlock::AvailabilityPending { block_root, block }) + Ok(MaybeAvailableBlock::AvailabilityPending { + block_root, + block, + custody_columns_count, + }) }; } Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, + blob_data: AvailableBlockData::NoData, blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), })) } @@ -434,42 +410,48 @@ impl DataAvailabilityChecker { } for block in blocks { + let custody_columns_count = block.custody_columns_count(); let (block_root, block, blobs, data_columns) = block.deconstruct(); let maybe_available_block = if self.blobs_required_for_block(&block) { - if blobs.is_some() { + if let Some(blobs) = blobs { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs, + blob_data: AvailableBlockData::Blobs(blobs), blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), }) } else { - MaybeAvailableBlock::AvailabilityPending { block_root, block } + MaybeAvailableBlock::AvailabilityPending { + block_root, + block, + custody_columns_count, + } } } else if self.data_columns_required_for_block(&block) { - if data_columns.is_some() { + if let Some(data_columns) = data_columns { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - data_columns: data_columns.map(|data_columns| { - data_columns.into_iter().map(|d| d.into_inner()).collect() - }), + blob_data: AvailableBlockData::DataColumns( + data_columns.into_iter().map(|d| d.into_inner()).collect(), + ), blobs_available_timestamp: None, spec: self.spec.clone(), }) } else { - MaybeAvailableBlock::AvailabilityPending { block_root, block } + MaybeAvailableBlock::AvailabilityPending { + block_root, + block, + custody_columns_count, + } } } else { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - data_columns: None, + blob_data: AvailableBlockData::NoData, blobs_available_timestamp: None, spec: self.spec.clone(), }) @@ -545,11 +527,11 @@ impl DataAvailabilityChecker { &self, block_root: &Hash256, ) -> Result, AvailabilityCheckError> { - let pending_components = match self + let verified_data_columns = match self .availability_cache .check_and_set_reconstruction_started(block_root) { - ReconstructColumnsDecision::Yes(pending_components) => pending_components, + ReconstructColumnsDecision::Yes(verified_data_columns) => verified_data_columns, ReconstructColumnsDecision::No(reason) => { return Ok(DataColumnReconstructionResult::NotStarted(reason)); } @@ -560,15 +542,14 @@ impl DataAvailabilityChecker { let all_data_columns = KzgVerifiedCustodyDataColumn::reconstruct_columns( &self.kzg, - &pending_components.verified_data_columns, + &verified_data_columns, &self.spec, ) .map_err(|e| { error!( - self.log, - "Error reconstructing data columns"; - "block_root" => ?block_root, - "error" => ?e + ?block_root, + error = ?e, + "Error reconstructing data columns" ); self.availability_cache .handle_reconstruction_failure(block_root); @@ -603,14 +584,15 @@ impl DataAvailabilityChecker { data_columns_to_publish.len() as u64, ); - debug!(self.log, "Reconstructed columns"; - "count" => data_columns_to_publish.len(), - "block_root" => ?block_root, - "slot" => slot, + debug!( + count = data_columns_to_publish.len(), + ?block_root, + %slot, + "Reconstructed columns" ); self.availability_cache - .put_kzg_verified_data_columns(*block_root, data_columns_to_publish.clone(), &self.log) + .put_kzg_verified_data_columns(*block_root, data_columns_to_publish.clone()) .map(|availability| { DataColumnReconstructionResult::Success(( availability, @@ -637,14 +619,18 @@ pub fn start_availability_cache_maintenance_service( if chain.spec.deneb_fork_epoch.is_some() { let overflow_cache = chain.data_availability_checker.availability_cache.clone(); executor.spawn( - async move { availability_cache_maintenance_service(chain, overflow_cache).await }, + async move { + availability_cache_maintenance_service(chain, overflow_cache) + .instrument(info_span!( + "DataAvailabilityChecker", + service = "data_availability_checker" + )) + .await + }, "availability_cache_service", ); } else { - debug!( - chain.log, - "Deneb fork not configured, not starting availability cache maintenance service" - ); + debug!("Deneb fork not configured, not starting availability cache maintenance service"); } } @@ -668,10 +654,7 @@ async fn availability_cache_maintenance_service( break; }; - debug!( - chain.log, - "Availability cache maintenance service firing"; - ); + debug!("Availability cache maintenance service firing"); let Some(current_epoch) = chain .slot_clock .now() @@ -701,11 +684,11 @@ async fn availability_cache_maintenance_service( ); if let Err(e) = overflow_cache.do_maintenance(cutoff_epoch) { - error!(chain.log, "Failed to maintain availability cache"; "error" => ?e); + error!(error = ?e,"Failed to maintain availability cache"); } } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. tokio::time::sleep(chain.slot_clock.slot_duration()).await; } @@ -713,13 +696,25 @@ async fn availability_cache_maintenance_service( } } +#[derive(Debug)] +pub enum AvailableBlockData { + /// Block is pre-Deneb or has zero blobs + NoData, + /// Block is post-Deneb, pre-PeerDAS and has more than zero blobs + Blobs(BlobSidecarList), + /// Block is post-PeerDAS and has more than zero blobs + DataColumns(DataColumnSidecarList), + /// Block is post-PeerDAS, has more than zero blobs and we recomputed the columns from the EL's + /// mempool blobs + DataColumnsRecv(oneshot::Receiver>), +} + /// A fully available block that is ready to be imported into fork choice. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug)] pub struct AvailableBlock { block_root: Hash256, block: Arc>, - blobs: Option>, - data_columns: Option>, + blob_data: AvailableBlockData, /// Timestamp at which this block first became available (UNIX timestamp, time since 1970). blobs_available_timestamp: Option, pub spec: Arc, @@ -729,15 +724,13 @@ impl AvailableBlock { pub fn __new_for_testing( block_root: Hash256, block: Arc>, - blobs: Option>, - data_columns: Option>, + data: AvailableBlockData, spec: Arc, ) -> Self { Self { block_root, block, - blobs, - data_columns, + blob_data: data, blobs_available_timestamp: None, spec, } @@ -750,39 +743,56 @@ impl AvailableBlock { self.block.clone() } - pub fn blobs(&self) -> Option<&BlobSidecarList> { - self.blobs.as_ref() - } - pub fn blobs_available_timestamp(&self) -> Option { self.blobs_available_timestamp } - pub fn data_columns(&self) -> Option<&DataColumnSidecarList> { - self.data_columns.as_ref() + pub fn data(&self) -> &AvailableBlockData { + &self.blob_data + } + + pub fn has_blobs(&self) -> bool { + match self.blob_data { + AvailableBlockData::NoData => false, + AvailableBlockData::Blobs(..) => true, + AvailableBlockData::DataColumns(_) => false, + AvailableBlockData::DataColumnsRecv(_) => false, + } } #[allow(clippy::type_complexity)] - pub fn deconstruct( - self, - ) -> ( - Hash256, - Arc>, - Option>, - Option>, - ) { + pub fn deconstruct(self) -> (Hash256, Arc>, AvailableBlockData) { let AvailableBlock { block_root, block, - blobs, - data_columns, + blob_data, .. } = self; - (block_root, block, blobs, data_columns) + (block_root, block, blob_data) + } + + /// Only used for testing + pub fn __clone_without_recv(&self) -> Result { + Ok(Self { + block_root: self.block_root, + block: self.block.clone(), + blob_data: match &self.blob_data { + AvailableBlockData::NoData => AvailableBlockData::NoData, + AvailableBlockData::Blobs(blobs) => AvailableBlockData::Blobs(blobs.clone()), + AvailableBlockData::DataColumns(data_columns) => { + AvailableBlockData::DataColumns(data_columns.clone()) + } + AvailableBlockData::DataColumnsRecv(_) => { + return Err("Can't clone DataColumnsRecv".to_owned()) + } + }, + blobs_available_timestamp: self.blobs_available_timestamp, + spec: self.spec.clone(), + }) } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum MaybeAvailableBlock { /// This variant is fully available. /// i.e. for pre-deneb blocks, it contains a (`SignedBeaconBlock`, `Blobs::None`) and for @@ -792,6 +802,7 @@ pub enum MaybeAvailableBlock { AvailabilityPending { block_root: Hash256, block: Arc>, + custody_columns_count: usize, }, } diff --git a/beacon_node/beacon_chain/src/data_availability_checker/error.rs b/beacon_node/beacon_chain/src/data_availability_checker/error.rs index 1ab85ab105..d091d6fefb 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/error.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/error.rs @@ -10,7 +10,7 @@ pub enum Error { blob_commitment: KzgCommitment, block_commitment: KzgCommitment, }, - Unexpected, + Unexpected(String), SszTypes(ssz_types::Error), MissingBlobs, MissingCustodyColumns, @@ -40,7 +40,7 @@ impl Error { | Error::MissingCustodyColumns | Error::StoreError(_) | Error::DecodeError(_) - | Error::Unexpected + | Error::Unexpected(_) | Error::ParentStateMissing(_) | Error::BlockReplayError(_) | Error::RebuildingStateCaches(_) diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index 7592ffd149..f38a3b8b9c 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -1,4 +1,5 @@ use super::state_lru_cache::{DietAvailabilityPendingExecutedBlock, StateLRUCache}; +use super::AvailableBlockData; use crate::beacon_chain::BeaconStore; use crate::blob_verification::KzgVerifiedBlob; use crate::block_verification_types::{ @@ -9,10 +10,11 @@ use crate::data_column_verification::KzgVerifiedCustodyDataColumn; use crate::BeaconChainTypes; use lru::LruCache; use parking_lot::RwLock; -use slog::{debug, Logger}; +use std::cmp::Ordering; use std::num::NonZeroUsize; use std::sync::Arc; use tokio::sync::oneshot; +use tracing::debug; use types::blob_sidecar::BlobIdentifier; use types::{ BlobSidecar, ChainSpec, ColumnIndex, DataColumnIdentifier, DataColumnSidecar, @@ -39,19 +41,6 @@ pub struct PendingComponents { } impl PendingComponents { - /// Clones the `PendingComponent` without cloning `data_column_recv`, as `Receiver` is not cloneable. - /// This should only be used when the receiver is no longer needed. - pub fn clone_without_column_recv(&self) -> Self { - PendingComponents { - block_root: self.block_root, - verified_blobs: self.verified_blobs.clone(), - verified_data_columns: self.verified_data_columns.clone(), - executed_block: self.executed_block.clone(), - reconstruction_started: self.reconstruction_started, - data_column_recv: None, - } - } - /// Returns an immutable reference to the cached block. pub fn get_cached_block(&self) -> &Option> { &self.executed_block @@ -95,26 +84,6 @@ impl PendingComponents { .unwrap_or(false) } - /// Returns the number of blobs that are expected to be present. Returns `None` if we don't have a - /// block. - /// - /// This corresponds to the number of commitments that are present in a block. - pub fn block_kzg_commitments_count(&self) -> Option { - self.get_cached_block() - .as_ref() - .map(|b| b.get_commitments().len()) - } - - /// Returns the number of blobs that have been received and are stored in the cache. - pub fn num_received_blobs(&self) -> usize { - self.get_cached_blobs().iter().flatten().count() - } - - /// Returns the number of data columns that have been received and are stored in the cache. - pub fn num_received_data_columns(&self) -> usize { - self.verified_data_columns.len() - } - /// Returns the indices of cached custody columns pub fn get_cached_data_columns_indices(&self) -> Vec { self.verified_data_columns @@ -189,51 +158,131 @@ impl PendingComponents { self.merge_blobs(reinsert); } - /// Checks if the block and all of its expected blobs or custody columns (post-PeerDAS) are - /// available in the cache. + /// Returns Some if the block has received all its required data for import. The return value + /// must be persisted in the DB along with the block. /// - /// Returns `true` if both the block exists and the number of received blobs / custody columns - /// matches the number of expected blobs / custody columns. - pub fn is_available(&self, custody_column_count: usize, log: &Logger) -> bool { - let block_kzg_commitments_count_opt = self.block_kzg_commitments_count(); - let expected_blobs_msg = block_kzg_commitments_count_opt - .as_ref() - .map(|num| num.to_string()) - .unwrap_or("unknown".to_string()); + /// WARNING: This function can potentially take a lot of time if the state needs to be + /// reconstructed from disk. Ensure you are not holding any write locks while calling this. + pub fn make_available( + &mut self, + spec: &Arc, + recover: R, + ) -> Result>, AvailabilityCheckError> + where + R: FnOnce( + DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError>, + { + let Some(block) = &self.executed_block else { + // Block not available yet + return Ok(None); + }; - // No data columns when there are 0 blobs - let expected_columns_opt = block_kzg_commitments_count_opt.map(|blob_count| { - if blob_count > 0 { - custody_column_count - } else { - 0 + let num_expected_blobs = block.num_blobs_expected(); + + let blob_data = if num_expected_blobs == 0 { + Some(AvailableBlockData::NoData) + } else if spec.is_peer_das_enabled_for_epoch(block.epoch()) { + let num_received_columns = self.verified_data_columns.len(); + let num_expected_columns = block.custody_columns_count(); + match num_received_columns.cmp(&num_expected_columns) { + Ordering::Greater => { + // Should never happen + return Err(AvailabilityCheckError::Unexpected(format!( + "too many columns got {num_received_columns} expected {num_expected_columns}" + ))); + } + Ordering::Equal => { + // Block is post-peerdas, and we got enough columns + let data_columns = self + .verified_data_columns + .iter() + .map(|d| d.clone().into_inner()) + .collect::>(); + Some(AvailableBlockData::DataColumns(data_columns)) + } + Ordering::Less => { + // The data_columns_recv is an infallible promise that we will receive all expected + // columns, so we consider the block available. + // We take the receiver as it can't be cloned, and make_available should never + // be called again once it returns `Some`. + self.data_column_recv + .take() + .map(AvailableBlockData::DataColumnsRecv) + } } - }); - let expected_columns_msg = expected_columns_opt - .as_ref() - .map(|num| num.to_string()) - .unwrap_or("unknown".to_string()); + } else { + // Before PeerDAS, blobs + let num_received_blobs = self.verified_blobs.iter().flatten().count(); + match num_received_blobs.cmp(&num_expected_blobs) { + Ordering::Greater => { + // Should never happen + return Err(AvailabilityCheckError::Unexpected(format!( + "too many blobs got {num_received_blobs} expected {num_expected_blobs}" + ))); + } + Ordering::Equal => { + let max_blobs = spec.max_blobs_per_block(block.epoch()) as usize; + let blobs_vec = self + .verified_blobs + .iter() + .flatten() + .map(|blob| blob.clone().to_blob()) + .collect::>(); + let blobs_len = blobs_vec.len(); + let blobs = RuntimeVariableList::new(blobs_vec, max_blobs).map_err(|_| { + AvailabilityCheckError::Unexpected(format!( + "over max_blobs len {blobs_len} max {max_blobs}" + )) + })?; + Some(AvailableBlockData::Blobs(blobs)) + } + Ordering::Less => { + // Not enough blobs received yet + None + } + } + }; - let num_received_blobs = self.num_received_blobs(); - let num_received_columns = self.num_received_data_columns(); + // Block's data not available yet + let Some(blob_data) = blob_data else { + return Ok(None); + }; - debug!( - log, - "Component(s) added to data availability checker"; - "block_root" => ?self.block_root, - "received_blobs" => num_received_blobs, - "expected_blobs" => expected_blobs_msg, - "received_columns" => num_received_columns, - "expected_columns" => expected_columns_msg, - ); + // Block is available, construct `AvailableExecutedBlock` - let all_blobs_received = block_kzg_commitments_count_opt - .is_some_and(|num_expected_blobs| num_expected_blobs == num_received_blobs); + let blobs_available_timestamp = match blob_data { + AvailableBlockData::NoData => None, + AvailableBlockData::Blobs(_) => self + .verified_blobs + .iter() + .flatten() + .map(|blob| blob.seen_timestamp()) + .max(), + // TODO(das): To be fixed with https://github.com/sigp/lighthouse/pull/6850 + AvailableBlockData::DataColumns(_) => None, + AvailableBlockData::DataColumnsRecv(_) => None, + }; - let all_columns_received = expected_columns_opt - .is_some_and(|num_expected_columns| num_expected_columns == num_received_columns); + let AvailabilityPendingExecutedBlock { + block, + import_data, + payload_verification_outcome, + custody_columns_count: _, + } = recover(block.clone())?; - all_blobs_received || all_columns_received + let available_block = AvailableBlock { + block_root: self.block_root, + block, + blob_data, + blobs_available_timestamp, + spec: spec.clone(), + }; + Ok(Some(AvailableExecutedBlock::new( + available_block, + import_data, + payload_verification_outcome, + ))) } /// Returns an empty `PendingComponents` object with the given block root. @@ -248,88 +297,6 @@ impl PendingComponents { } } - /// Verifies an `SignedBeaconBlock` against a set of KZG verified blobs. - /// This does not check whether a block *should* have blobs, these checks should have been - /// completed when producing the `AvailabilityPendingBlock`. - /// - /// WARNING: This function can potentially take a lot of time if the state needs to be - /// reconstructed from disk. Ensure you are not holding any write locks while calling this. - pub fn make_available( - self, - spec: &Arc, - recover: R, - ) -> Result, AvailabilityCheckError> - where - R: FnOnce( - DietAvailabilityPendingExecutedBlock, - ) -> Result, AvailabilityCheckError>, - { - let Self { - block_root, - verified_blobs, - verified_data_columns, - executed_block, - data_column_recv, - .. - } = self; - - let blobs_available_timestamp = verified_blobs - .iter() - .flatten() - .map(|blob| blob.seen_timestamp()) - .max(); - - let Some(diet_executed_block) = executed_block else { - return Err(AvailabilityCheckError::Unexpected); - }; - - let is_peer_das_enabled = spec.is_peer_das_enabled_for_epoch(diet_executed_block.epoch()); - let (blobs, data_columns) = if is_peer_das_enabled { - let data_columns = verified_data_columns - .into_iter() - .map(|d| d.into_inner()) - .collect::>(); - (None, Some(data_columns)) - } else { - let num_blobs_expected = diet_executed_block.num_blobs_expected(); - let Some(verified_blobs) = verified_blobs - .into_iter() - .map(|b| b.map(|b| b.to_blob())) - .take(num_blobs_expected) - .collect::>>() - .map(Into::into) - else { - return Err(AvailabilityCheckError::Unexpected); - }; - let max_len = spec.max_blobs_per_block(diet_executed_block.as_block().epoch()) as usize; - ( - Some(RuntimeVariableList::new(verified_blobs, max_len)?), - None, - ) - }; - let executed_block = recover(diet_executed_block)?; - - let AvailabilityPendingExecutedBlock { - block, - mut import_data, - payload_verification_outcome, - } = executed_block; - - import_data.data_column_recv = data_column_recv; - - let available_block = AvailableBlock { - block_root, - block, - blobs, - data_columns, - blobs_available_timestamp, - spec: spec.clone(), - }; - Ok(Availability::Available(Box::new( - AvailableExecutedBlock::new(available_block, import_data, payload_verification_outcome), - ))) - } - /// Returns the epoch of the block if it is cached, otherwise returns the epoch of the first blob. pub fn epoch(&self) -> Option { self.executed_block @@ -355,6 +322,41 @@ impl PendingComponents { None }) } + + pub fn status_str(&self, block_epoch: Epoch, spec: &ChainSpec) -> String { + let block_count = if self.executed_block.is_some() { 1 } else { 0 }; + if spec.is_peer_das_enabled_for_epoch(block_epoch) { + let custody_columns_count = if let Some(block) = self.get_cached_block() { + &block.custody_columns_count().to_string() + } else { + "?" + }; + let data_column_recv_count = if self.data_column_recv.is_some() { + 1 + } else { + 0 + }; + format!( + "block {} data_columns {}/{} data_columns_recv {}", + block_count, + self.verified_data_columns.len(), + custody_columns_count, + data_column_recv_count, + ) + } else { + let num_expected_blobs = if let Some(block) = self.get_cached_block() { + &block.num_blobs_expected().to_string() + } else { + "?" + }; + format!( + "block {} blobs {}/{}", + block_count, + self.verified_blobs.len(), + num_expected_blobs + ) + } + } } /// This is the main struct for this module. Outside methods should @@ -365,8 +367,6 @@ pub struct DataAvailabilityCheckerInner { /// This cache holds a limited number of states in memory and reconstructs them /// from disk when necessary. This is necessary until we merge tree-states state_cache: StateLRUCache, - /// The number of data columns the node is sampling via subnet sampling. - sampling_column_count: usize, spec: Arc, } @@ -375,7 +375,7 @@ pub struct DataAvailabilityCheckerInner { // the current usage, as it's deconstructed immediately. #[allow(clippy::large_enum_variant)] pub(crate) enum ReconstructColumnsDecision { - Yes(PendingComponents), + Yes(Vec>), No(&'static str), } @@ -383,21 +383,15 @@ impl DataAvailabilityCheckerInner { pub fn new( capacity: NonZeroUsize, beacon_store: BeaconStore, - sampling_column_count: usize, spec: Arc, ) -> Result { Ok(Self { critical: RwLock::new(LruCache::new(capacity)), state_cache: StateLRUCache::new(beacon_store, spec.clone()), - sampling_column_count, spec, }) } - pub fn sampling_column_count(&self) -> usize { - self.sampling_column_count - } - /// Returns true if the block root is known, without altering the LRU ordering pub fn get_execution_valid_block( &self, @@ -456,17 +450,10 @@ impl DataAvailabilityCheckerInner { } /// Puts the KZG verified blobs into the availability cache as pending components. - /// - /// The `data_column_recv` parameter is an optional `Receiver` for data columns that are - /// computed asynchronously. This method remains **used** after PeerDAS activation, because - /// blocks can be made available if the EL already has the blobs and returns them via the - /// `getBlobsV1` engine method. More details in [fetch_blobs.rs](https://github.com/sigp/lighthouse/blob/44f8add41ea2252769bb967864af95b3c13af8ca/beacon_node/beacon_chain/src/fetch_blobs.rs). pub fn put_kzg_verified_blobs>>( &self, block_root: Hash256, kzg_verified_blobs: I, - data_column_recv: Option>>, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut kzg_verified_blobs = kzg_verified_blobs.into_iter().peekable(); @@ -475,7 +462,7 @@ impl DataAvailabilityCheckerInner { .map(|verified_blob| verified_blob.as_blob().epoch()) else { // Verified blobs list should be non-empty. - return Err(AvailabilityCheckError::Unexpected); + return Err(AvailabilityCheckError::Unexpected("empty blobs".to_owned())); }; let mut fixed_blobs = @@ -500,21 +487,21 @@ impl DataAvailabilityCheckerInner { // Merge in the blobs. pending_components.merge_blobs(fixed_blobs); - if data_column_recv.is_some() { - // If `data_column_recv` is `Some`, it means we have all the blobs from engine, and have - // started computing data columns. We store the receiver in `PendingComponents` for - // later use when importing the block. - pending_components.data_column_recv = data_column_recv; - } + debug!( + component = "blobs", + ?block_root, + status = pending_components.status_str(epoch, &self.spec), + "Component added to data availability checker" + ); - if pending_components.is_available(self.sampling_column_count, log) { + if let Some(available_block) = pending_components.make_available(&self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -528,7 +515,6 @@ impl DataAvailabilityCheckerInner { &self, block_root: Hash256, kzg_verified_data_columns: I, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut kzg_verified_data_columns = kzg_verified_data_columns.into_iter().peekable(); let Some(epoch) = kzg_verified_data_columns @@ -536,7 +522,9 @@ impl DataAvailabilityCheckerInner { .map(|verified_blob| verified_blob.as_data_column().epoch()) else { // Verified data_columns list should be non-empty. - return Err(AvailabilityCheckError::Unexpected); + return Err(AvailabilityCheckError::Unexpected( + "empty columns".to_owned(), + )); }; let mut write_lock = self.critical.write(); @@ -552,14 +540,69 @@ impl DataAvailabilityCheckerInner { // Merge in the data columns. pending_components.merge_data_columns(kzg_verified_data_columns)?; - if pending_components.is_available(self.sampling_column_count, log) { + debug!( + component = "data_columns", + ?block_root, + status = pending_components.status_str(epoch, &self.spec), + "Component added to data availability checker" + ); + + if let Some(available_block) = pending_components.make_available(&self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) + } else { + write_lock.put(block_root, pending_components); + Ok(Availability::MissingComponents(block_root)) + } + } + + /// The `data_column_recv` parameter is a `Receiver` for data columns that are computed + /// asynchronously. This method is used if the EL already has the blobs and returns them via the + /// `getBlobsV1` engine method. More details in [fetch_blobs.rs](https://github.com/sigp/lighthouse/blob/44f8add41ea2252769bb967864af95b3c13af8ca/beacon_node/beacon_chain/src/fetch_blobs.rs). + pub fn put_computed_data_columns_recv( + &self, + block_root: Hash256, + block_epoch: Epoch, + data_column_recv: oneshot::Receiver>, + ) -> Result, AvailabilityCheckError> { + let mut write_lock = self.critical.write(); + + // Grab existing entry or create a new entry. + let mut pending_components = write_lock + .pop_entry(&block_root) + .map(|(_, v)| v) + .unwrap_or_else(|| { + PendingComponents::empty( + block_root, + self.spec.max_blobs_per_block(block_epoch) as usize, + ) + }); + + // We have all the blobs from engine, and have started computing data columns. We store the + // receiver in `PendingComponents` for later use when importing the block. + // TODO(das): Error or log if we overwrite a prior receiver https://github.com/sigp/lighthouse/issues/6764 + pending_components.data_column_recv = Some(data_column_recv); + + debug!( + component = "data_columns_recv", + ?block_root, + status = pending_components.status_str(block_epoch, &self.spec), + "Component added to data availability checker" + ); + + if let Some(available_block) = pending_components.make_available(&self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? { + // We keep the pending components in the availability cache during block import (#5845). + // `data_column_recv` is returned as part of the available block and is no longer needed here. + write_lock.put(block_root, pending_components); + drop(write_lock); + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -586,16 +629,12 @@ impl DataAvailabilityCheckerInner { }; // If we're sampling all columns, it means we must be custodying all columns. - let custody_column_count = self.sampling_column_count(); let total_column_count = self.spec.number_of_columns as usize; let received_column_count = pending_components.verified_data_columns.len(); if pending_components.reconstruction_started { return ReconstructColumnsDecision::No("already started"); } - if custody_column_count != total_column_count { - return ReconstructColumnsDecision::No("not required for full node"); - } if received_column_count >= total_column_count { return ReconstructColumnsDecision::No("all columns received"); } @@ -604,7 +643,7 @@ impl DataAvailabilityCheckerInner { } pending_components.reconstruction_started = true; - ReconstructColumnsDecision::Yes(pending_components.clone_without_column_recv()) + ReconstructColumnsDecision::Yes(pending_components.verified_data_columns.clone()) } /// This could mean some invalid data columns made it through to the `DataAvailabilityChecker`. @@ -622,7 +661,6 @@ impl DataAvailabilityCheckerInner { pub fn put_pending_executed_block( &self, executed_block: AvailabilityPendingExecutedBlock, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut write_lock = self.critical.write(); let epoch = executed_block.as_block().epoch(); @@ -644,15 +682,22 @@ impl DataAvailabilityCheckerInner { // Merge in the block. pending_components.merge_block(diet_executed_block); + debug!( + component = "block", + ?block_root, + status = pending_components.status_str(epoch, &self.spec), + "Component added to data availability checker" + ); + // Check if we have all components and entire set is consistent. - if pending_components.is_available(self.sampling_column_count, log) { + if let Some(available_block) = pending_components.make_available(&self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -716,13 +761,12 @@ mod test { test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType}, }; use fork_choice::PayloadVerificationStatus; - - use logging::test_logger; - use slog::{info, Logger}; + use logging::create_test_tracing_subscriber; use state_processing::ConsensusContext; use std::collections::VecDeque; use store::{database::interface::BeaconNodeBackend, HotColdDB, ItemStore, StoreConfig}; use tempfile::{tempdir, TempDir}; + use tracing::info; use types::non_zero_usize::new_non_zero_usize; use types::{ExecPayload, MinimalEthSpec}; @@ -732,7 +776,6 @@ mod test { fn get_store_with_spec( db_path: &TempDir, spec: Arc, - log: Logger, ) -> Arc, BeaconNodeBackend>> { let hot_path = db_path.path().join("hot_db"); let cold_path = db_path.path().join("cold_db"); @@ -746,14 +789,12 @@ mod test { |_, _, _| Ok(()), config, spec, - log, ) .expect("disk store should initialize") } // get a beacon chain harness advanced to just before deneb fork async fn get_deneb_chain( - log: Logger, db_path: &TempDir, ) -> BeaconChainHarness> { let altair_fork_epoch = Epoch::new(1); @@ -770,12 +811,11 @@ mod test { spec.deneb_fork_epoch = Some(deneb_fork_epoch); let spec = Arc::new(spec); - let chain_store = get_store_with_spec::(db_path, spec.clone(), log.clone()); + let chain_store = get_store_with_spec::(db_path, spec.clone()); let validators_keypairs = types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT); let harness = BeaconChainHarness::builder(E::default()) .spec(spec.clone()) - .logger(log.clone()) .keypairs(validators_keypairs) .fresh_disk_store(chain_store) .mock_execution_layer() @@ -818,7 +858,6 @@ mod test { Cold: ItemStore, { let chain = &harness.chain; - let log = chain.log.clone(); let head = chain.head_snapshot(); let parent_state = head.beacon_state.clone(); @@ -846,7 +885,7 @@ mod test { ); // log kzg commitments - info!(log, "printing kzg commitments"); + info!("printing kzg commitments"); for comm in Vec::from( block .message() @@ -855,9 +894,9 @@ mod test { .expect("should be deneb fork") .clone(), ) { - info!(log, "kzg commitment"; "commitment" => ?comm); + info!(commitment = ?comm, "kzg commitment"); } - info!(log, "done printing kzg commitments"); + info!("done printing kzg commitments"); let gossip_verified_blobs = if let Some((kzg_proofs, blobs)) = maybe_blobs { let sidecars = @@ -883,7 +922,6 @@ mod test { parent_eth1_finalization_data, confirmed_state_roots: vec![], consensus_context, - data_column_recv: None, }; let payload_verification_outcome = PayloadVerificationOutcome { @@ -895,6 +933,7 @@ mod test { block, import_data, payload_verification_outcome, + custody_columns_count: DEFAULT_TEST_CUSTODY_COLUMN_COUNT, }; (availability_pending_block, gossip_verified_blobs) @@ -915,20 +954,15 @@ mod test { EthSpec = E, >, { - let log = test_logger(); + create_test_tracing_subscriber(); let chain_db_path = tempdir().expect("should get temp dir"); - let harness = get_deneb_chain(log.clone(), &chain_db_path).await; + let harness = get_deneb_chain(&chain_db_path).await; let spec = harness.spec.clone(); let test_store = harness.chain.store.clone(); let capacity_non_zero = new_non_zero_usize(capacity); let cache = Arc::new( - DataAvailabilityCheckerInner::::new( - capacity_non_zero, - test_store, - DEFAULT_TEST_CUSTODY_COLUMN_COUNT, - spec.clone(), - ) - .expect("should create cache"), + DataAvailabilityCheckerInner::::new(capacity_non_zero, test_store, spec.clone()) + .expect("should create cache"), ); (harness, cache, chain_db_path) } @@ -951,7 +985,7 @@ mod test { ); assert!(cache.critical.read().is_empty(), "cache should be empty"); let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); if blobs_expected == 0 { assert!( @@ -990,7 +1024,7 @@ mod test { for (blob_index, gossip_blob) in blobs.into_iter().enumerate() { kzg_verified_blobs.push(gossip_blob.into_inner()); let availability = cache - .put_kzg_verified_blobs(root, kzg_verified_blobs.clone(), None, harness.logger()) + .put_kzg_verified_blobs(root, kzg_verified_blobs.clone()) .expect("should put blob"); if blob_index == blobs_expected - 1 { assert!(matches!(availability, Availability::Available(_))); @@ -1018,17 +1052,16 @@ mod test { for gossip_blob in blobs { kzg_verified_blobs.push(gossip_blob.into_inner()); let availability = cache - .put_kzg_verified_blobs(root, kzg_verified_blobs.clone(), None, harness.logger()) + .put_kzg_verified_blobs(root, kzg_verified_blobs.clone()) .expect("should put blob"); - assert_eq!( - availability, - Availability::MissingComponents(root), + assert!( + matches!(availability, Availability::MissingComponents(_)), "should be pending block" ); assert_eq!(cache.critical.read().len(), 1); } let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); assert!( matches!(availability, Availability::Available(_)), @@ -1096,7 +1129,7 @@ mod test { // put the block in the cache let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); // grab the diet block from the cache for later testing @@ -1274,12 +1307,13 @@ mod pending_components_tests { }, confirmed_state_roots: vec![], consensus_context: ConsensusContext::new(Slot::new(0)), - data_column_recv: None, }, payload_verification_outcome: PayloadVerificationOutcome { payload_verification_status: PayloadVerificationStatus::Verified, is_valid_merge_transition_block: false, }, + // Default custody columns count, doesn't matter here + custody_columns_count: 8, }; (block.into(), blobs, invalid_blobs) } diff --git a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs index 2a2a0431cc..09d0563a4a 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs @@ -29,6 +29,7 @@ pub struct DietAvailabilityPendingExecutedBlock { confirmed_state_roots: Vec, consensus_context: OnDiskConsensusContext, payload_verification_outcome: PayloadVerificationOutcome, + custody_columns_count: usize, } /// just implementing the same methods as `AvailabilityPendingExecutedBlock` @@ -58,6 +59,10 @@ impl DietAvailabilityPendingExecutedBlock { .unwrap_or_default() } + pub fn custody_columns_count(&self) -> usize { + self.custody_columns_count + } + /// Returns the epoch corresponding to `self.slot()`. pub fn epoch(&self) -> Epoch { self.block.slot().epoch(E::slots_per_epoch()) @@ -108,6 +113,7 @@ impl StateLRUCache { executed_block.import_data.consensus_context, ), payload_verification_outcome: executed_block.payload_verification_outcome, + custody_columns_count: executed_block.custody_columns_count, } } @@ -136,9 +142,9 @@ impl StateLRUCache { consensus_context: diet_executed_block .consensus_context .into_consensus_context(), - data_column_recv: None, }, payload_verification_outcome: diet_executed_block.payload_verification_outcome, + custody_columns_count: diet_executed_block.custody_columns_count, }) } @@ -226,6 +232,7 @@ impl From> value.import_data.consensus_context, ), payload_verification_outcome: value.payload_verification_outcome, + custody_columns_count: value.custody_columns_count, } } } diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 1262fcdeb8..2f95d834b5 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -10,12 +10,12 @@ use fork_choice::ProtoBlock; use kzg::{Error as KzgError, Kzg}; use proto_array::Block; use slasher::test_utils::E; -use slog::debug; use slot_clock::SlotClock; use ssz_derive::{Decode, Encode}; use std::iter; use std::marker::PhantomData; use std::sync::Arc; +use tracing::debug; use types::data_column_sidecar::{ColumnIndex, DataColumnIdentifier}; use types::{ BeaconStateError, ChainSpec, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, @@ -580,10 +580,9 @@ fn verify_proposer_and_signature( (proposer.index, proposer.fork) } else { debug!( - chain.log, - "Proposer shuffling cache miss for column verification"; - "block_root" => %block_root, - "index" => %column_index, + %block_root, + index = %column_index, + "Proposer shuffling cache miss for column verification" ); let (parent_state_root, mut parent_state) = chain .store diff --git a/beacon_node/beacon_chain/src/early_attester_cache.rs b/beacon_node/beacon_chain/src/early_attester_cache.rs index c94ea0e941..a90911026c 100644 --- a/beacon_node/beacon_chain/src/early_attester_cache.rs +++ b/beacon_node/beacon_chain/src/early_attester_cache.rs @@ -1,4 +1,4 @@ -use crate::data_availability_checker::AvailableBlock; +use crate::data_availability_checker::{AvailableBlock, AvailableBlockData}; use crate::{ attester_cache::{CommitteeLengths, Error}, metrics, @@ -52,7 +52,7 @@ impl EarlyAttesterCache { pub fn add_head_block( &self, beacon_block_root: Hash256, - block: AvailableBlock, + block: &AvailableBlock, proto_block: ProtoBlock, state: &BeaconState, spec: &ChainSpec, @@ -70,14 +70,23 @@ impl EarlyAttesterCache { }, }; - let (_, block, blobs, data_columns) = block.deconstruct(); + let (blobs, data_columns) = match block.data() { + AvailableBlockData::NoData => (None, None), + AvailableBlockData::Blobs(blobs) => (Some(blobs.clone()), None), + AvailableBlockData::DataColumns(data_columns) => (None, Some(data_columns.clone())), + // TODO(das): Once the columns are received, they will not be available in + // the early attester cache. If someone does a query to us via RPC we + // will get downscored. + AvailableBlockData::DataColumnsRecv(_) => (None, None), + }; + let item = CacheItem { epoch, committee_lengths, beacon_block_root, source, target, - block, + block: block.block_cloned(), blobs, data_columns, proto_block, diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 2e13ab4090..8509c52c8a 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -61,6 +61,7 @@ pub enum BeaconChainError { ForkChoiceStoreError(ForkChoiceStoreError), MissingBeaconBlock(Hash256), MissingBeaconState(Hash256), + MissingHotStateSummary(Hash256), SlotProcessingError(SlotProcessingError), EpochProcessingError(EpochProcessingError), StateAdvanceError(StateAdvanceError), @@ -181,9 +182,9 @@ pub enum BeaconChainError { execution_block_hash: Option, }, ForkchoiceUpdate(execution_layer::Error), - FinalizedCheckpointMismatch { - head_state: Checkpoint, - fork_choice: Hash256, + InvalidCheckpoint { + state_root: Hash256, + checkpoint: Checkpoint, }, InvalidSlot(Slot), HeadBlockNotFullyVerified { diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs index ad4f106517..43429b726c 100644 --- a/beacon_node/beacon_chain/src/eth1_chain.rs +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -3,7 +3,6 @@ use eth1::{Config as Eth1Config, Eth1Block, Service as HttpService}; use eth2::lighthouse::Eth1SyncStatusData; use ethereum_hashing::hash; use int_to_bytes::int_to_bytes32; -use slog::{debug, error, trace, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use state_processing::per_block_processing::get_new_eth1_data; @@ -14,6 +13,7 @@ use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use store::{DBColumn, Error as StoreError, StoreItem}; use task_executor::TaskExecutor; +use tracing::{debug, error, trace}; use types::{ BeaconState, BeaconStateError, ChainSpec, Deposit, Eth1Data, EthSpec, Hash256, Slot, Unsigned, }; @@ -283,11 +283,9 @@ where pub fn from_ssz_container( ssz_container: &SszEth1, config: Eth1Config, - log: &Logger, spec: Arc, ) -> Result { - let backend = - Eth1ChainBackend::from_bytes(&ssz_container.backend_bytes, config, log.clone(), spec)?; + let backend = Eth1ChainBackend::from_bytes(&ssz_container.backend_bytes, config, spec)?; Ok(Self { use_dummy_backend: ssz_container.use_dummy_backend, backend, @@ -351,12 +349,7 @@ pub trait Eth1ChainBackend: Sized + Send + Sync { fn as_bytes(&self) -> Vec; /// Create a `Eth1ChainBackend` instance given encoded bytes. - fn from_bytes( - bytes: &[u8], - config: Eth1Config, - log: Logger, - spec: Arc, - ) -> Result; + fn from_bytes(bytes: &[u8], config: Eth1Config, spec: Arc) -> Result; } /// Provides a simple, testing-only backend that generates deterministic, meaningless eth1 data. @@ -412,7 +405,6 @@ impl Eth1ChainBackend for DummyEth1ChainBackend { fn from_bytes( _bytes: &[u8], _config: Eth1Config, - _log: Logger, _spec: Arc, ) -> Result { Ok(Self(PhantomData)) @@ -433,7 +425,6 @@ impl Default for DummyEth1ChainBackend { #[derive(Clone)] pub struct CachingEth1Backend { pub core: HttpService, - log: Logger, _phantom: PhantomData, } @@ -441,11 +432,10 @@ impl CachingEth1Backend { /// Instantiates `self` with empty caches. /// /// Does not connect to the eth1 node or start any tasks to keep the cache updated. - pub fn new(config: Eth1Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Eth1Config, spec: Arc) -> Result { Ok(Self { - core: HttpService::new(config, log.clone(), spec) + core: HttpService::new(config, spec) .map_err(|e| format!("Failed to create eth1 http service: {:?}", e))?, - log, _phantom: PhantomData, }) } @@ -458,7 +448,6 @@ impl CachingEth1Backend { /// Instantiates `self` from an existing service. pub fn from_service(service: HttpService) -> Self { Self { - log: service.log.clone(), core: service, _phantom: PhantomData, } @@ -481,9 +470,8 @@ impl Eth1ChainBackend for CachingEth1Backend { }; trace!( - self.log, - "Found eth1 data votes_to_consider"; - "votes_to_consider" => votes_to_consider.len(), + votes_to_consider = votes_to_consider.len(), + "Found eth1 data votes_to_consider" ); let valid_votes = collect_valid_votes(state, &votes_to_consider); @@ -500,22 +488,20 @@ impl Eth1ChainBackend for CachingEth1Backend { .map(|vote| { let vote = vote.0.clone(); debug!( - self.log, - "No valid eth1_data votes"; - "outcome" => "Casting vote corresponding to last candidate eth1 block", - "vote" => ?vote + outcome = "Casting vote corresponding to last candidate eth1 block", + ?vote, + "No valid eth1_data votes" ); vote }) .unwrap_or_else(|| { let vote = state.eth1_data().clone(); error!( - self.log, - "No valid eth1_data votes, `votes_to_consider` empty"; - "lowest_block_number" => self.core.lowest_block_number(), - "earliest_block_timestamp" => self.core.earliest_block_timestamp(), - "genesis_time" => state.genesis_time(), - "outcome" => "casting `state.eth1_data` as eth1 vote" + lowest_block_number = self.core.lowest_block_number(), + earliest_block_timestamp = self.core.earliest_block_timestamp(), + genesis_time = state.genesis_time(), + outcome = "casting `state.eth1_data` as eth1 vote", + "No valid eth1_data votes, `votes_to_consider` empty" ); metrics::inc_counter(&metrics::DEFAULT_ETH1_VOTES); vote @@ -523,11 +509,10 @@ impl Eth1ChainBackend for CachingEth1Backend { }; debug!( - self.log, - "Produced vote for eth1 chain"; - "deposit_root" => format!("{:?}", eth1_data.deposit_root), - "deposit_count" => eth1_data.deposit_count, - "block_hash" => format!("{:?}", eth1_data.block_hash), + deposit_root = ?eth1_data.deposit_root, + deposit_count = eth1_data.deposit_count, + block_hash = ?eth1_data.block_hash, + "Produced vote for eth1 chain" ); Ok(eth1_data) @@ -592,16 +577,10 @@ impl Eth1ChainBackend for CachingEth1Backend { } /// Recover the cached backend from encoded bytes. - fn from_bytes( - bytes: &[u8], - config: Eth1Config, - log: Logger, - spec: Arc, - ) -> Result { - let inner = HttpService::from_bytes(bytes, config, log.clone(), spec)?; + fn from_bytes(bytes: &[u8], config: Eth1Config, spec: Arc) -> Result { + let inner = HttpService::from_bytes(bytes, config, spec)?; Ok(Self { core: inner, - log, _phantom: PhantomData, }) } @@ -742,17 +721,18 @@ mod test { mod eth1_chain_json_backend { use super::*; use eth1::DepositLog; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use types::{test_utils::generate_deterministic_keypair, MainnetEthSpec}; fn get_eth1_chain() -> Eth1Chain, E> { + create_test_tracing_subscriber(); + let eth1_config = Eth1Config { ..Eth1Config::default() }; - let log = test_logger(); Eth1Chain::new( - CachingEth1Backend::new(eth1_config, log, Arc::new(MainnetEthSpec::default_spec())) + CachingEth1Backend::new(eth1_config, Arc::new(MainnetEthSpec::default_spec())) .unwrap(), ) } diff --git a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs index 24b6542eab..84618ceab0 100644 --- a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs +++ b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs @@ -1,7 +1,7 @@ -use slog::{debug, Logger}; use ssz_derive::{Decode, Encode}; use std::cmp; use std::collections::BTreeMap; +use tracing::debug; use types::{Checkpoint, Epoch, Eth1Data, Hash256 as Root}; /// The default size of the cache. @@ -104,28 +104,27 @@ pub struct Eth1FinalizationCache { by_checkpoint: CheckpointMap, pending_eth1: BTreeMap, last_finalized: Option, - log: Logger, +} + +impl Default for Eth1FinalizationCache { + fn default() -> Self { + Self { + by_checkpoint: CheckpointMap::new(), + pending_eth1: BTreeMap::new(), + last_finalized: None, + } + } } /// Provides a cache of `Eth1CacheData` at epoch boundaries. This is used to /// finalize deposits when a new epoch is finalized. /// impl Eth1FinalizationCache { - pub fn new(log: Logger) -> Self { - Eth1FinalizationCache { - by_checkpoint: CheckpointMap::new(), - pending_eth1: BTreeMap::new(), - last_finalized: None, - log, - } - } - - pub fn with_capacity(log: Logger, capacity: usize) -> Self { + pub fn with_capacity(capacity: usize) -> Self { Eth1FinalizationCache { by_checkpoint: CheckpointMap::with_capacity(capacity), pending_eth1: BTreeMap::new(), last_finalized: None, - log, } } @@ -136,10 +135,9 @@ impl Eth1FinalizationCache { eth1_finalization_data.eth1_data.clone(), ); debug!( - self.log, - "Eth1Cache: inserted pending eth1"; - "eth1_data.deposit_count" => eth1_finalization_data.eth1_data.deposit_count, - "eth1_deposit_index" => eth1_finalization_data.eth1_deposit_index, + eth1_data.deposit_count = eth1_finalization_data.eth1_data.deposit_count, + eth1_deposit_index = eth1_finalization_data.eth1_deposit_index, + "Eth1Cache: inserted pending eth1" ); } self.by_checkpoint @@ -154,10 +152,8 @@ impl Eth1FinalizationCache { if finalized_deposit_index >= pending_count { result = self.pending_eth1.remove(&pending_count); debug!( - self.log, - "Eth1Cache: dropped pending eth1"; - "pending_count" => pending_count, - "finalized_deposit_index" => finalized_deposit_index, + pending_count, + finalized_deposit_index, "Eth1Cache: dropped pending eth1" ); } else { break; @@ -172,9 +168,8 @@ impl Eth1FinalizationCache { self.last_finalized.clone() } else { debug!( - self.log, - "Eth1Cache: cache miss"; - "epoch" => checkpoint.epoch, + epoch = %checkpoint.epoch, + "Eth1Cache: cache miss" ); None } @@ -194,8 +189,6 @@ impl Eth1FinalizationCache { #[cfg(test)] pub mod tests { use super::*; - use sloggers::null::NullLoggerBuilder; - use sloggers::Build; use std::collections::HashMap; const SLOTS_PER_EPOCH: u64 = 32; @@ -203,8 +196,7 @@ pub mod tests { const EPOCHS_PER_ETH1_VOTING_PERIOD: u64 = 64; fn eth1cache() -> Eth1FinalizationCache { - let log_builder = NullLoggerBuilder; - Eth1FinalizationCache::new(log_builder.build().expect("should build log")) + Eth1FinalizationCache::default() } fn random_eth1_data(deposit_count: u64) -> Eth1Data { diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 8c342893ae..d09b74e645 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -1,7 +1,7 @@ pub use eth2::types::{EventKind, SseBlock, SseFinalizedCheckpoint, SseHead}; -use slog::{trace, Logger}; use tokio::sync::broadcast; use tokio::sync::broadcast::{error::SendError, Receiver, Sender}; +use tracing::trace; use types::EthSpec; const DEFAULT_CHANNEL_CAPACITY: usize = 16; @@ -25,18 +25,14 @@ pub struct ServerSentEventHandler { attester_slashing_tx: Sender>, bls_to_execution_change_tx: Sender>, block_gossip_tx: Sender>, - log: Logger, } impl ServerSentEventHandler { - pub fn new(log: Logger, capacity_multiplier: usize) -> Self { - Self::new_with_capacity( - log, - capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY), - ) + pub fn new(capacity_multiplier: usize) -> Self { + Self::new_with_capacity(capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY)) } - pub fn new_with_capacity(log: Logger, capacity: usize) -> Self { + pub fn new_with_capacity(capacity: usize) -> Self { let (attestation_tx, _) = broadcast::channel(capacity); let (single_attestation_tx, _) = broadcast::channel(capacity); let (block_tx, _) = broadcast::channel(capacity); @@ -75,17 +71,15 @@ impl ServerSentEventHandler { attester_slashing_tx, bls_to_execution_change_tx, block_gossip_tx, - log, } } pub fn register(&self, kind: EventKind) { let log_count = |name, count| { trace!( - self.log, - "Registering server-sent event"; - "kind" => name, - "receiver_count" => count + kind = name, + receiver_count = count, + "Registering server-sent event" ); }; let result = match &kind { @@ -163,7 +157,7 @@ impl ServerSentEventHandler { .map(|count| log_count("block gossip", count)), }; if let Err(SendError(event)) = result { - trace!(self.log, "No receivers registered to listen for event"; "event" => ?event); + trace!(?event, "No receivers registered to listen for event"); } } diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 720f98e298..1da8cb413b 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -17,7 +17,6 @@ use execution_layer::{ }; use fork_choice::{InvalidationOperation, PayloadVerificationStatus}; use proto_array::{Block as ProtoBlock, ExecutionStatus}; -use slog::{debug, warn}; use slot_clock::SlotClock; use state_processing::per_block_processing::{ compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled, @@ -25,6 +24,7 @@ use state_processing::per_block_processing::{ }; use std::sync::Arc; use tokio::task::JoinHandle; +use tracing::{debug, warn}; use tree_hash::TreeHash; use types::payload::BlockProductionVersion; use types::*; @@ -85,11 +85,10 @@ impl PayloadNotifier { block_message.try_into()?; if let Err(e) = new_payload_request.perform_optimistic_sync_verifications() { warn!( - chain.log, - "Falling back to slow block hash verification"; - "block_number" => ?block_message.execution_payload().map(|payload| payload.block_number()), - "info" => "you can silence this warning with --disable-optimistic-finalized-sync", - "error" => ?e, + block_number = ?block_message.execution_payload().map(|payload| payload.block_number()), + info = "you can silence this warning with --disable-optimistic-finalized-sync", + error = ?e, + "Falling back to slow block hash verification" ); None } else { @@ -150,16 +149,15 @@ async fn notify_new_payload( ref validation_error, } => { warn!( - chain.log, - "Invalid execution payload"; - "validation_error" => ?validation_error, - "latest_valid_hash" => ?latest_valid_hash, - "execution_block_hash" => ?execution_block_hash, - "root" => ?block.tree_hash_root(), - "graffiti" => block.body().graffiti().as_utf8_lossy(), - "proposer_index" => block.proposer_index(), - "slot" => block.slot(), - "method" => "new_payload", + ?validation_error, + ?latest_valid_hash, + ?execution_block_hash, + root = ?block.tree_hash_root(), + graffiti = block.body().graffiti().as_utf8_lossy(), + proposer_index = block.proposer_index(), + slot = %block.slot(), + method = "new_payload", + "Invalid execution payload" ); // Only trigger payload invalidation in fork choice if the @@ -197,15 +195,14 @@ async fn notify_new_payload( ref validation_error, } => { warn!( - chain.log, - "Invalid execution payload block hash"; - "validation_error" => ?validation_error, - "execution_block_hash" => ?execution_block_hash, - "root" => ?block.tree_hash_root(), - "graffiti" => block.body().graffiti().as_utf8_lossy(), - "proposer_index" => block.proposer_index(), - "slot" => block.slot(), - "method" => "new_payload", + ?validation_error, + ?execution_block_hash, + root = ?block.tree_hash_root(), + graffiti = block.body().graffiti().as_utf8_lossy(), + proposer_index = block.proposer_index(), + slot = %block.slot(), + method = "new_payload", + "Invalid execution payload block hash" ); // Returning an error here should be sufficient to invalidate the block. We have no @@ -278,10 +275,9 @@ pub async fn validate_merge_block( None => { if allow_optimistic_import == AllowOptimisticImport::Yes { debug!( - chain.log, - "Optimistically importing merge transition block"; - "block_hash" => ?execution_payload.parent_hash(), - "msg" => "the terminal block/parent was unavailable" + block_hash = ?execution_payload.parent_hash(), + msg = "the terminal block/parent was unavailable", + "Optimistically importing merge transition block" ); Ok(()) } else { diff --git a/beacon_node/beacon_chain/src/fetch_blobs.rs b/beacon_node/beacon_chain/src/fetch_blobs.rs index 6e365f936d..ceb563ffc2 100644 --- a/beacon_node/beacon_chain/src/fetch_blobs.rs +++ b/beacon_node/beacon_chain/src/fetch_blobs.rs @@ -14,11 +14,11 @@ use crate::{metrics, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes use execution_layer::json_structures::BlobAndProofV1; use execution_layer::Error as ExecutionLayerError; use metrics::{inc_counter, inc_counter_by, TryExt}; -use slog::{debug, error, o, Logger}; use ssz_types::FixedVector; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use std::sync::Arc; use tokio::sync::oneshot; +use tracing::{debug, error}; use types::blob_sidecar::{BlobSidecarError, FixedBlobSidecarList}; use types::{ BeaconStateError, BlobSidecar, ChainSpec, DataColumnSidecar, DataColumnSidecarList, EthSpec, @@ -50,11 +50,6 @@ pub async fn fetch_and_process_engine_blobs( block: Arc>>, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, ) -> Result, FetchEngineBlobError> { - let block_root_str = format!("{:?}", block_root); - let log = chain - .log - .new(o!("service" => "fetch_engine_blobs", "block_root" => block_root_str)); - let versioned_hashes = if let Some(kzg_commitments) = block .message() .body() @@ -67,10 +62,7 @@ pub async fn fetch_and_process_engine_blobs( .map(kzg_commitment_to_versioned_hash) .collect::>() } else { - debug!( - log, - "Fetch blobs not triggered - none required"; - ); + debug!("Fetch blobs not triggered - none required"); return Ok(None); }; @@ -81,22 +73,14 @@ pub async fn fetch_and_process_engine_blobs( .as_ref() .ok_or(FetchEngineBlobError::ExecutionLayerMissing)?; - debug!( - log, - "Fetching blobs from the EL"; - "num_expected_blobs" => num_expected_blobs, - ); + debug!(num_expected_blobs, "Fetching blobs from the EL"); let response = execution_layer .get_blobs(versioned_hashes) .await .map_err(FetchEngineBlobError::RequestFailed)?; if response.is_empty() || response.iter().all(|opt| opt.is_none()) { - debug!( - log, - "No blobs fetched from the EL"; - "num_expected_blobs" => num_expected_blobs, - ); + debug!(num_expected_blobs, "No blobs fetched from the EL"); inc_counter(&metrics::BLOBS_FROM_EL_MISS_TOTAL); return Ok(None); } else { @@ -154,11 +138,8 @@ pub async fn fetch_and_process_engine_blobs( // Partial blobs response isn't useful for PeerDAS, so we don't bother building and publishing data columns. if num_fetched_blobs != num_expected_blobs { debug!( - log, - "Not all blobs fetched from the EL"; - "info" => "Unable to compute data columns", - "num_fetched_blobs" => num_fetched_blobs, - "num_expected_blobs" => num_expected_blobs, + info = "Unable to compute data columns", + num_fetched_blobs, num_expected_blobs, "Not all blobs fetched from the EL" ); return Ok(None); } @@ -170,9 +151,21 @@ pub async fn fetch_and_process_engine_blobs( { // Avoid computing columns if block has already been imported. debug!( - log, - "Ignoring EL blobs response"; - "info" => "block has already been imported", + info = "block has already been imported", + "Ignoring EL blobs response" + ); + return Ok(None); + } + + if chain + .canonical_head + .fork_choice_read_lock() + .contains_block(&block_root) + { + // Avoid computing columns if block has already been imported. + debug!( + info = "block has already been imported", + "Ignoring EL blobs response" ); return Ok(None); } @@ -182,7 +175,6 @@ pub async fn fetch_and_process_engine_blobs( block.clone(), fixed_blob_sidecar_list.clone(), publish_fn, - log.clone(), ); Some(data_columns_receiver) @@ -194,11 +186,7 @@ pub async fn fetch_and_process_engine_blobs( None }; - debug!( - log, - "Processing engine blobs"; - "num_fetched_blobs" => num_fetched_blobs, - ); + debug!(num_fetched_blobs, "Processing engine blobs"); let availability_processing_status = chain .process_engine_blobs( @@ -226,7 +214,6 @@ fn spawn_compute_and_publish_data_columns_task( block: Arc>>, blobs: FixedBlobSidecarList, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, - log: Logger, ) -> oneshot::Receiver>>> { let chain_cloned = chain.clone(); let (data_columns_sender, data_columns_receiver) = oneshot::channel(); @@ -254,9 +241,8 @@ fn spawn_compute_and_publish_data_columns_task( Ok(d) => d, Err(e) => { error!( - log, - "Failed to build data column sidecars from blobs"; - "error" => ?e + error = ?e, + "Failed to build data column sidecars from blobs" ); return; } @@ -266,20 +252,10 @@ fn spawn_compute_and_publish_data_columns_task( // Data column receiver have been dropped - block may have already been imported. // This race condition exists because gossip columns may arrive and trigger block // import during the computation. Here we just drop the computed columns. - debug!( - log, - "Failed to send computed data columns"; - ); + debug!("Failed to send computed data columns"); return; }; - // At the moment non supernodes are not required to publish any columns. - // TODO(das): we could experiment with having full nodes publish their custodied - // columns here. - if !chain_cloned.data_availability_checker.is_supernode() { - return; - } - publish_fn(BlobsOrDataColumns::DataColumns(all_data_columns)); }, "compute_and_publish_data_columns", diff --git a/beacon_node/beacon_chain/src/fork_revert.rs b/beacon_node/beacon_chain/src/fork_revert.rs index 8d1c29f46f..cde2950c89 100644 --- a/beacon_node/beacon_chain/src/fork_revert.rs +++ b/beacon_node/beacon_chain/src/fork_revert.rs @@ -1,7 +1,6 @@ use crate::{BeaconForkChoiceStore, BeaconSnapshot}; use fork_choice::{ForkChoice, PayloadVerificationStatus}; use itertools::process_results; -use slog::{info, warn, Logger}; use state_processing::state_advance::complete_state_advance; use state_processing::{ per_block_processing, per_block_processing::BlockSignatureStrategy, ConsensusContext, @@ -10,6 +9,7 @@ use state_processing::{ use std::sync::Arc; use std::time::Duration; use store::{iter::ParentRootBlockIterator, HotColdDB, ItemStore}; +use tracing::{info, warn}; use types::{BeaconState, ChainSpec, EthSpec, ForkName, Hash256, SignedBeaconBlock, Slot}; const CORRUPT_DB_MESSAGE: &str = "The database could be corrupt. Check its file permissions or \ @@ -27,7 +27,6 @@ pub fn revert_to_fork_boundary, Cold: ItemStore head_block_root: Hash256, store: Arc>, spec: &ChainSpec, - log: &Logger, ) -> Result<(Hash256, SignedBeaconBlock), String> { let current_fork = spec.fork_name_at_slot::(current_slot); let fork_epoch = spec @@ -42,10 +41,9 @@ pub fn revert_to_fork_boundary, Cold: ItemStore } warn!( - log, - "Reverting invalid head block"; - "target_fork" => %current_fork, - "fork_epoch" => fork_epoch, + target_fork = %current_fork, + %fork_epoch, + "Reverting invalid head block" ); let block_iter = ParentRootBlockIterator::fork_tolerant(&store, head_block_root); @@ -55,10 +53,9 @@ pub fn revert_to_fork_boundary, Cold: ItemStore Some((block_root, block)) } else { info!( - log, - "Reverting block"; - "block_root" => ?block_root, - "slot" => block.slot(), + ?block_root, + slot = %block.slot(), + "Reverting block" ); None } @@ -116,8 +113,9 @@ pub fn reset_fork_choice_to_finalization, Cold: It // Advance finalized state to finalized epoch (to handle skipped slots). let finalized_state_root = finalized_block.state_root(); + // The enshrined finalized state should be in the state cache. let mut finalized_state = store - .get_state(&finalized_state_root, Some(finalized_block.slot())) + .get_state(&finalized_state_root, Some(finalized_block.slot()), true) .map_err(|e| format!("Error loading finalized state: {:?}", e))? .ok_or_else(|| { format!( diff --git a/beacon_node/beacon_chain/src/graffiti_calculator.rs b/beacon_node/beacon_chain/src/graffiti_calculator.rs index 8692d374ed..23d1d69b1c 100644 --- a/beacon_node/beacon_chain/src/graffiti_calculator.rs +++ b/beacon_node/beacon_chain/src/graffiti_calculator.rs @@ -1,11 +1,12 @@ use crate::BeaconChain; use crate::BeaconChainTypes; use execution_layer::{http::ENGINE_GET_CLIENT_VERSION_V1, CommitPrefix, ExecutionLayer}; +use logging::crit; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, warn, Logger}; use slot_clock::SlotClock; use std::{fmt::Debug, time::Duration}; use task_executor::TaskExecutor; +use tracing::{debug, error, warn}; use types::{EthSpec, Graffiti, GRAFFITI_BYTES_LEN}; const ENGINE_VERSION_AGE_LIMIT_EPOCH_MULTIPLE: u32 = 6; // 6 epochs @@ -51,7 +52,6 @@ pub struct GraffitiCalculator { pub beacon_graffiti: GraffitiOrigin, execution_layer: Option>, pub epoch_duration: Duration, - log: Logger, } impl GraffitiCalculator { @@ -59,13 +59,11 @@ impl GraffitiCalculator { beacon_graffiti: GraffitiOrigin, execution_layer: Option>, epoch_duration: Duration, - log: Logger, ) -> Self { Self { beacon_graffiti, execution_layer, epoch_duration, - log, } } @@ -86,7 +84,7 @@ impl GraffitiCalculator { let Some(execution_layer) = self.execution_layer.as_ref() else { // Return default graffiti if there is no execution layer. This // shouldn't occur if we're actually producing blocks. - crit!(self.log, "No execution layer available for graffiti calculation during block production!"); + crit!("No execution layer available for graffiti calculation during block production!"); return default_graffiti; }; @@ -101,7 +99,7 @@ impl GraffitiCalculator { { Ok(engine_versions) => engine_versions, Err(el_error) => { - warn!(self.log, "Failed to determine execution engine version for graffiti"; "error" => ?el_error); + warn!(error = ?el_error, "Failed to determine execution engine version for graffiti"); return default_graffiti; } }; @@ -109,9 +107,8 @@ impl GraffitiCalculator { let Some(engine_version) = engine_versions.first() else { // Got an empty array which indicates the EL doesn't support the method debug!( - self.log, "Using default lighthouse graffiti: EL does not support {} method", - ENGINE_GET_CLIENT_VERSION_V1; + ENGINE_GET_CLIENT_VERSION_V1 ); return default_graffiti; }; @@ -119,19 +116,20 @@ impl GraffitiCalculator { // More than one version implies lighthouse is connected to // an EL multiplexer. We don't support modifying the graffiti // with these configurations. - warn!( - self.log, - "Execution Engine multiplexer detected, using default graffiti" - ); + warn!("Execution Engine multiplexer detected, using default graffiti"); return default_graffiti; } - let lighthouse_commit_prefix = CommitPrefix::try_from(lighthouse_version::COMMIT_PREFIX.to_string()) - .unwrap_or_else(|error_message| { - // This really shouldn't happen but we want to definitly log if it does - crit!(self.log, "Failed to parse lighthouse commit prefix"; "error" => error_message); - CommitPrefix("00000000".to_string()) - }); + let lighthouse_commit_prefix = + CommitPrefix::try_from(lighthouse_version::COMMIT_PREFIX.to_string()) + .unwrap_or_else(|error_message| { + // This really shouldn't happen but we want to definitly log if it does + crit!( + error = error_message, + "Failed to parse lighthouse commit prefix" + ); + CommitPrefix("00000000".to_string()) + }); engine_version.calculate_graffiti(lighthouse_commit_prefix) } @@ -144,36 +142,24 @@ pub fn start_engine_version_cache_refresh_service( executor: TaskExecutor, ) { let Some(el_ref) = chain.execution_layer.as_ref() else { - debug!( - chain.log, - "No execution layer configured, not starting engine version cache refresh service" - ); + debug!("No execution layer configured, not starting engine version cache refresh service"); return; }; if matches!( chain.graffiti_calculator.beacon_graffiti, GraffitiOrigin::UserSpecified(_) ) { - debug!( - chain.log, - "Graffiti is user-specified, not starting engine version cache refresh service" - ); + debug!("Graffiti is user-specified, not starting engine version cache refresh service"); return; } let execution_layer = el_ref.clone(); - let log = chain.log.clone(); let slot_clock = chain.slot_clock.clone(); let epoch_duration = chain.graffiti_calculator.epoch_duration; executor.spawn( async move { - engine_version_cache_refresh_service::( - execution_layer, - slot_clock, - epoch_duration, - log, - ) - .await + engine_version_cache_refresh_service::(execution_layer, slot_clock, epoch_duration) + .await }, "engine_version_cache_refresh_service", ); @@ -183,13 +169,15 @@ async fn engine_version_cache_refresh_service( execution_layer: ExecutionLayer, slot_clock: T::SlotClock, epoch_duration: Duration, - log: Logger, ) { // Preload the engine version cache after a brief delay to allow for EL initialization. // This initial priming ensures cache readiness before the service's regular update cycle begins. tokio::time::sleep(ENGINE_VERSION_CACHE_PRELOAD_STARTUP_DELAY).await; if let Err(e) = execution_layer.get_engine_version(None).await { - debug!(log, "Failed to preload engine version cache"; "error" => format!("{:?}", e)); + debug!( + error = ?e, + "Failed to preload engine version cache" + ); } // this service should run 3/8 of the way through the epoch @@ -203,18 +191,14 @@ async fn engine_version_cache_refresh_service( let firing_delay = partial_firing_delay + duration_to_next_epoch + epoch_delay; tokio::time::sleep(firing_delay).await; - debug!( - log, - "Engine version cache refresh service firing"; - ); + debug!("Engine version cache refresh service firing"); match execution_layer.get_engine_version(None).await { - Err(e) => warn!(log, "Failed to populate engine version cache"; "error" => ?e), + Err(e) => warn!( error = ?e, "Failed to populate engine version cache"), Ok(versions) => { if versions.is_empty() { // Empty array indicates the EL doesn't support the method debug!( - log, "EL does not support {} method. Sleeping twice as long before retry", ENGINE_GET_CLIENT_VERSION_V1 ); @@ -227,7 +211,7 @@ async fn engine_version_cache_refresh_service( } } None => { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. tokio::time::sleep(slot_clock.slot_duration()).await; } @@ -241,10 +225,10 @@ mod tests { use crate::ChainConfig; use execution_layer::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_ENGINE_CAPABILITIES}; use execution_layer::EngineCapabilities; - use slog::info; use std::sync::Arc; use std::sync::LazyLock; use std::time::Duration; + use tracing::info; use types::{ChainSpec, Graffiti, Keypair, MinimalEthSpec, GRAFFITI_BYTES_LEN}; const VALIDATOR_COUNT: usize = 48; @@ -261,7 +245,6 @@ mod tests { .spec(spec) .chain_config(chain_config.unwrap_or_default()) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(logging::test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .build(); @@ -302,7 +285,10 @@ mod tests { let graffiti_str = std::str::from_utf8(graffiti_slice).expect("bytes should convert nicely to ascii"); - info!(harness.chain.log, "results"; "lighthouse_version" => lighthouse_version::VERSION, "graffiti_str" => graffiti_str); + info!( + lighthouse_version = lighthouse_version::VERSION, + graffiti_str, "results" + ); println!("lighthouse_version: '{}'", lighthouse_version::VERSION); println!("graffiti_str: '{}'", graffiti_str); @@ -339,7 +325,7 @@ mod tests { std::str::from_utf8(&found_graffiti_bytes[..expected_graffiti_prefix_len]) .expect("bytes should convert nicely to ascii"); - info!(harness.chain.log, "results"; "expected_graffiti_string" => &expected_graffiti_string, "found_graffiti_string" => &found_graffiti_string); + info!(expected_graffiti_string, found_graffiti_string, "results"); println!("expected_graffiti_string: '{}'", expected_graffiti_string); println!("found_graffiti_string: '{}'", found_graffiti_string); diff --git a/beacon_node/beacon_chain/src/historical_blocks.rs b/beacon_node/beacon_chain/src/historical_blocks.rs index a48f32e7b4..ee51964910 100644 --- a/beacon_node/beacon_chain/src/historical_blocks.rs +++ b/beacon_node/beacon_chain/src/historical_blocks.rs @@ -1,7 +1,6 @@ -use crate::data_availability_checker::AvailableBlock; +use crate::data_availability_checker::{AvailableBlock, AvailableBlockData}; use crate::{metrics, BeaconChain, BeaconChainTypes}; use itertools::Itertools; -use slog::debug; use state_processing::{ per_block_processing::ParallelSignatureSets, signature_sets::{block_proposal_signature_set_from_parts, Error as SignatureSetError}, @@ -12,6 +11,7 @@ use std::time::Duration; use store::metadata::DataColumnInfo; use store::{AnchorInfo, BlobInfo, DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp}; use strum::IntoStaticStr; +use tracing::debug; use types::{FixedBytesExtended, Hash256, Slot}; /// Use a longer timeout on the pubkey cache. @@ -82,11 +82,10 @@ impl BeaconChain { if blocks_to_import.len() != total_blocks { debug!( - self.log, - "Ignoring some historic blocks"; - "oldest_block_slot" => anchor_info.oldest_block_slot, - "total_blocks" => total_blocks, - "ignored" => total_blocks.saturating_sub(blocks_to_import.len()), + oldest_block_slot = %anchor_info.oldest_block_slot, + total_blocks, + ignored = total_blocks.saturating_sub(blocks_to_import.len()), + "Ignoring some historic blocks" ); } @@ -94,34 +93,18 @@ impl BeaconChain { return Ok(0); } - // Blobs are stored per block, and data columns are each stored individually - let n_blob_ops_per_block = if self.spec.is_peer_das_scheduled() { - // TODO(das): `available_block includes all sampled columns, but we only need to store - // custody columns. To be clarified in spec PR. - self.data_availability_checker.get_sampling_column_count() - } else { - 1 - }; - - let blob_batch_size = blocks_to_import - .iter() - .filter(|available_block| available_block.blobs().is_some()) - .count() - .saturating_mul(n_blob_ops_per_block); - let mut expected_block_root = anchor_info.oldest_block_parent; let mut prev_block_slot = anchor_info.oldest_block_slot; let mut new_oldest_blob_slot = blob_info.oldest_blob_slot; let mut new_oldest_data_column_slot = data_column_info.oldest_data_column_slot; - let mut blob_batch = Vec::with_capacity(blob_batch_size); + let mut blob_batch = Vec::::new(); let mut cold_batch = Vec::with_capacity(blocks_to_import.len()); let mut hot_batch = Vec::with_capacity(blocks_to_import.len()); let mut signed_blocks = Vec::with_capacity(blocks_to_import.len()); for available_block in blocks_to_import.into_iter().rev() { - let (block_root, block, maybe_blobs, maybe_data_columns) = - available_block.deconstruct(); + let (block_root, block, block_data) = available_block.deconstruct(); if block_root != expected_block_root { return Err(HistoricalBlockError::MismatchedBlockRoot { @@ -144,17 +127,26 @@ impl BeaconChain { ); } - // Store the blobs too - if let Some(blobs) = maybe_blobs { - new_oldest_blob_slot = Some(block.slot()); - self.store - .blobs_as_kv_store_ops(&block_root, blobs, &mut blob_batch); + match &block_data { + AvailableBlockData::NoData => {} + AvailableBlockData::Blobs(..) => { + new_oldest_blob_slot = Some(block.slot()); + } + AvailableBlockData::DataColumns(_) | AvailableBlockData::DataColumnsRecv(_) => { + new_oldest_data_column_slot = Some(block.slot()); + } } - // Store the data columns too - if let Some(data_columns) = maybe_data_columns { - new_oldest_data_column_slot = Some(block.slot()); - self.store - .data_columns_as_kv_store_ops(&block_root, data_columns, &mut blob_batch); + + // Store the blobs or data columns too + if let Some(op) = self + .get_blobs_or_columns_store_op(block_root, block_data) + .map_err(|e| { + HistoricalBlockError::StoreError(StoreError::DBError { + message: format!("get_blobs_or_columns_store_op error {e:?}"), + }) + })? + { + blob_batch.extend(self.store.convert_to_kv_batch(vec![op])?); } // Store block roots, including at all skip slots in the freezer DB. diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index 78442d8df0..8e29be9732 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -2,12 +2,12 @@ use crate::errors::BeaconChainError; use crate::{metrics, BeaconChainTypes, BeaconStore}; use parking_lot::{Mutex, RwLock}; use safe_arith::SafeArith; -use slog::{debug, Logger}; use ssz::Decode; use std::num::NonZeroUsize; use std::sync::Arc; use store::DBColumn; use store::KeyValueStore; +use tracing::debug; use tree_hash::TreeHash; use types::non_zero_usize::new_non_zero_usize; use types::{ @@ -82,7 +82,6 @@ impl LightClientServerCache { block_slot: Slot, block_parent_root: &Hash256, sync_aggregate: &SyncAggregate, - log: &Logger, chain_spec: &ChainSpec, ) -> Result<(), BeaconChainError> { metrics::inc_counter(&metrics::LIGHT_CLIENT_SERVER_CACHE_PROCESSING_REQUESTS); @@ -170,9 +169,8 @@ impl LightClientServerCache { )?); } else { debug!( - log, - "Finalized block not available in store for light_client server"; - "finalized_block_root" => format!("{}", cached_parts.finalized_block_root), + finalized_block_root = %cached_parts.finalized_block_root, + "Finalized block not available in store for light_client server" ); } } @@ -319,8 +317,11 @@ impl LightClientServerCache { metrics::inc_counter(&metrics::LIGHT_CLIENT_SERVER_CACHE_PREV_BLOCK_CACHE_MISS); // Compute the value, handling potential errors. + // This state should already be cached. By electing not to cache it here + // we remove any chance of the light client server from affecting the state cache. + // We'd like the light client server to be as minimally invasive as possible. let mut state = store - .get_state(block_state_root, Some(block_slot))? + .get_state(block_state_root, Some(block_slot), false)? .ok_or_else(|| { BeaconChainError::DBInconsistent(format!("Missing state {:?}", block_state_root)) })?; diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index bc4b8e1ed8..cda5b34103 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -3,7 +3,6 @@ use crate::errors::BeaconChainError; use crate::head_tracker::{HeadTracker, SszHeadTracker}; use crate::persisted_beacon_chain::{PersistedBeaconChain, DUMMY_CANONICAL_HEAD_BLOCK_ROOT}; use parking_lot::Mutex; -use slog::{debug, error, info, warn, Logger}; use std::collections::{HashMap, HashSet}; use std::mem; use std::sync::{mpsc, Arc}; @@ -13,6 +12,7 @@ use store::hot_cold_store::{migrate_database, HotColdDBError}; use store::iter::RootsIterator; use store::{Error, ItemStore, StoreItem, StoreOp}; pub use store::{HotColdDB, MemoryStore}; +use tracing::{debug, error, info, warn}; use types::{ BeaconState, BeaconStateError, BeaconStateHash, Checkpoint, Epoch, EthSpec, FixedBytesExtended, Hash256, SignedBeaconBlockHash, Slot, @@ -44,7 +44,6 @@ pub struct BackgroundMigrator, Cold: ItemStore> tx_thread: Option, thread::JoinHandle<()>)>>, /// Genesis block root, for persisting the `PersistedBeaconChain`. genesis_block_root: Hash256, - log: Logger, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -124,14 +123,23 @@ pub enum Notification { Finalization(FinalizationNotification), Reconstruction, PruneBlobs(Epoch), + ManualFinalization(ManualFinalizationNotification), + ManualCompaction, +} + +pub struct ManualFinalizationNotification { + pub state_root: BeaconStateHash, + pub checkpoint: Checkpoint, + pub head_tracker: Arc, + pub genesis_block_root: Hash256, } pub struct FinalizationNotification { - finalized_state_root: BeaconStateHash, - finalized_checkpoint: Checkpoint, - head_tracker: Arc, - prev_migration: Arc>, - genesis_block_root: Hash256, + pub finalized_state_root: BeaconStateHash, + pub finalized_checkpoint: Checkpoint, + pub head_tracker: Arc, + pub prev_migration: Arc>, + pub genesis_block_root: Hash256, } impl, Cold: ItemStore> BackgroundMigrator { @@ -140,7 +148,6 @@ impl, Cold: ItemStore> BackgroundMigrator>, config: MigratorConfig, genesis_block_root: Hash256, - log: Logger, ) -> Self { // Estimate last migration run from DB split slot. let prev_migration = Arc::new(Mutex::new(PrevMigration { @@ -150,14 +157,13 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator>, opt_tx: Option>, - log: &Logger, ) { match db.reconstruct_historic_states(Some(BLOCKS_PER_RECONSTRUCTION)) { Ok(()) => { @@ -221,9 +242,8 @@ impl, Cold: ItemStore> BackgroundMigrator ?e + error = ?e, + "Unable to requeue reconstruction notification" ); } } @@ -231,24 +251,18 @@ impl, Cold: ItemStore> BackgroundMigrator { error!( - log, - "State reconstruction failed"; - "error" => ?e, + error = ?e, + "State reconstruction failed" ); } } } - pub fn run_prune_blobs( - db: Arc>, - data_availability_boundary: Epoch, - log: &Logger, - ) { + pub fn run_prune_blobs(db: Arc>, data_availability_boundary: Epoch) { if let Err(e) = db.try_prune_blobs(false, data_availability_boundary) { error!( - log, - "Blob pruning failed"; - "error" => ?e, + error = ?e, + "Blob pruning failed" ); } } @@ -264,7 +278,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator format!("{:?}", thread_err) + reason = ?thread_err, + "Migration thread died, so it was restarted" ); } @@ -289,22 +302,36 @@ impl, Cold: ItemStore> BackgroundMigrator>, - notif: FinalizationNotification, - log: &Logger, + notif: ManualFinalizationNotification, ) { + // We create a "dummy" prev migration + let prev_migration = PrevMigration { + epoch: Epoch::new(1), + epochs_per_migration: 2, + }; + let notif = FinalizationNotification { + finalized_state_root: notif.state_root, + finalized_checkpoint: notif.checkpoint, + head_tracker: notif.head_tracker, + prev_migration: Arc::new(prev_migration.into()), + genesis_block_root: notif.genesis_block_root, + }; + Self::run_migration(db, notif); + } + + /// Perform the actual work of `process_finalization`. + fn run_migration(db: Arc>, notif: FinalizationNotification) { // Do not run too frequently. let epoch = notif.finalized_checkpoint.epoch; let mut prev_migration = notif.prev_migration.lock(); if epoch < prev_migration.epoch + prev_migration.epochs_per_migration { debug!( - log, - "Database consolidation deferred"; - "last_finalized_epoch" => prev_migration.epoch, - "new_finalized_epoch" => epoch, - "epochs_per_migration" => prev_migration.epochs_per_migration, + last_finalized_epoch = %prev_migration.epoch, + new_finalized_epoch = %epoch, + epochs_per_migration = prev_migration.epochs_per_migration, + "Database consolidation deferred" ); return; } @@ -315,19 +342,19 @@ impl, Cold: ItemStore> BackgroundMigrator state, other => { error!( - log, - "Migrator failed to load state"; - "state_root" => ?finalized_state_root, - "error" => ?other + state_root = ?finalized_state_root, + error = ?other, + "Migrator failed to load state" ); return; } @@ -340,16 +367,14 @@ impl, Cold: ItemStore> BackgroundMigrator old_finalized_checkpoint, Ok(PruningOutcome::DeferredConcurrentHeadTrackerMutation) => { warn!( - log, - "Pruning deferred because of a concurrent mutation"; - "message" => "this is expected only very rarely!" + message = "this is expected only very rarely!", + "Pruning deferred because of a concurrent mutation" ); return; } @@ -358,16 +383,15 @@ impl, Cold: ItemStore> BackgroundMigrator { warn!( - log, - "Ignoring out of order finalization request"; - "old_finalized_epoch" => old_finalized_checkpoint.epoch, - "new_finalized_epoch" => new_finalized_checkpoint.epoch, - "message" => "this is expected occasionally due to a (harmless) race condition" + old_finalized_epoch = %old_finalized_checkpoint.epoch, + new_finalized_epoch = %new_finalized_checkpoint.epoch, + message = "this is expected occasionally due to a (harmless) race condition", + "Ignoring out of order finalization request" ); return; } Err(e) => { - warn!(log, "Block pruning failed"; "error" => ?e); + warn!(error = ?e,"Block pruning failed"); return; } }; @@ -381,17 +405,12 @@ impl, Cold: ItemStore> BackgroundMigrator {} Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => { debug!( - log, - "Database migration postponed, unaligned finalized block"; - "slot" => slot.as_u64() + slot = slot.as_u64(), + "Database migration postponed, unaligned finalized block" ); } Err(e) => { - warn!( - log, - "Database migration failed"; - "error" => format!("{:?}", e) - ); + warn!(error = ?e, "Database migration failed"); return; } }; @@ -401,12 +420,20 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", e)); + warn!(error = ?e, "Database compaction failed"); } - debug!(log, "Database consolidation complete"); + debug!("Database consolidation complete"); + } + + fn run_manual_compaction(db: Arc>) { + debug!("Running manual compaction"); + if let Err(error) = db.compact() { + warn!(?error, "Database compaction failed"); + } else { + debug!("Manual compaction completed"); + } } /// Spawn a new child thread to run the migration process. @@ -414,7 +441,6 @@ impl, Cold: ItemStore> BackgroundMigrator>, - log: Logger, ) -> (mpsc::Sender, thread::JoinHandle<()>) { let (tx, rx) = mpsc::channel(); let inner_tx = tx.clone(); @@ -422,16 +448,30 @@ impl, Cold: ItemStore> BackgroundMigrator reconstruction_notif = Some(notif), Notification::Finalization(fin) => finalization_notif = Some(fin), + Notification::ManualFinalization(fin) => manual_finalization_notif = Some(fin), Notification::PruneBlobs(dab) => prune_blobs_notif = Some(dab), + Notification::ManualCompaction => manual_compaction_notif = Some(notif), } // Read the rest of the messages in the channel, taking the best of each type. for notif in rx.try_iter() { match notif { Notification::Reconstruction => reconstruction_notif = Some(notif), + Notification::ManualCompaction => manual_compaction_notif = Some(notif), + Notification::ManualFinalization(fin) => { + if let Some(current) = manual_finalization_notif.as_mut() { + if fin.checkpoint.epoch > current.checkpoint.epoch { + *current = fin; + } + } else { + manual_finalization_notif = Some(fin); + } + } Notification::Finalization(fin) => { if let Some(current) = finalization_notif.as_mut() { if fin.finalized_checkpoint.epoch @@ -452,13 +492,19 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator, new_finalized_checkpoint: Checkpoint, genesis_block_root: Hash256, - log: &Logger, ) -> Result { let old_finalized_checkpoint = store @@ -515,10 +560,9 @@ impl, Cold: ItemStore> BackgroundMigrator old_finalized_checkpoint.epoch, - "new_finalized_epoch" => new_finalized_checkpoint.epoch, + old_finalized_epoch = %old_finalized_checkpoint.epoch, + new_finalized_epoch = %new_finalized_checkpoint.epoch, + "Starting database pruning" ); // For each slot between the new finalized checkpoint and the old finalized checkpoint, // collect the beacon block root and state root of the canonical chain. @@ -546,11 +590,10 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", old_finalized_checkpoint.root), - "new_finalized_root" => format!("{:?}", new_finalized_checkpoint.root), - "head_count" => heads.len(), + old_finalized_root = ?old_finalized_checkpoint.root, + new_finalized_root = ?new_finalized_checkpoint.root, + head_count = heads.len(), + "Extra pruning information" ); for (head_hash, head_slot) in heads { @@ -565,10 +608,9 @@ impl, Cold: ItemStore> BackgroundMigrator { warn!( - log, - "Forgetting invalid head block"; - "block_root" => ?head_hash, - "error" => ?e, + block_root = ?head_hash, + error = ?e, + "Forgetting invalid head block" ); abandoned_heads.insert(head_hash); continue; @@ -606,10 +648,9 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", head_hash), - "head_slot" => head_slot, + head_block_root = ?head_hash, + %head_slot, + "Found a chain that should already have been pruned" ); potentially_abandoned_head.take(); break; @@ -663,10 +704,9 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", abandoned_head), - "head_slot" => head_slot, + head_block_root = ?abandoned_head, + %head_slot, + "Pruning head" ); abandoned_heads.insert(abandoned_head); abandoned_blocks.extend( @@ -740,7 +780,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator>, old_finalized_epoch: Epoch, new_finalized_epoch: Epoch, - log: &Logger, ) -> Result<(), Error> { if !db.compact_on_prune() { return Ok(()); @@ -775,10 +814,9 @@ impl, Cold: ItemStore> BackgroundMigrator MIN_COMPACTION_PERIOD_SECONDS) { info!( - log, - "Starting database compaction"; - "old_finalized_epoch" => old_finalized_epoch, - "new_finalized_epoch" => new_finalized_epoch, + %old_finalized_epoch, + %new_finalized_epoch, + "Starting database compaction" ); db.compact()?; @@ -787,7 +825,7 @@ impl, Cold: ItemStore> BackgroundMigrator::EthSpec> + pub fn from_block(block: BeaconBlockRef) -> Self { + Self { + root: block.tree_hash_root(), + slot: block.slot(), + } + } + + pub fn root(&self) -> &Hash256 { + &self.root + } + + pub fn slot(&self) -> &Slot { + &self.slot + } + + pub fn persist_in_store(&self, store: A) -> Result<(), StoreError> + where + T: BeaconChainTypes, + A: AsRef>, + { + if store + .as_ref() + .item_exists::(&self.root)? + { + Ok(()) + } else { + store.as_ref().put_item(&self.root, self) + } + } + + pub fn remove_from_store(&self, store: A) -> Result<(), StoreError> + where + T: BeaconChainTypes, + A: AsRef>, + { + store + .as_ref() + .hot_db + .key_delete(OTBColumn.into(), self.root.as_slice()) + } + + fn is_canonical( + &self, + chain: &BeaconChain, + ) -> Result { + Ok(chain + .forwards_iter_block_roots_until(self.slot, self.slot)? + .next() + .transpose()? + .map(|(root, _)| root) + == Some(self.root)) + } +} + +impl StoreItem for OptimisticTransitionBlock { + fn db_column() -> DBColumn { + OTBColumn + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Ok(Self::from_ssz_bytes(bytes)?) + } +} + +/// The routine is expected to run once per epoch, 1/4th through the epoch. +pub const EPOCH_DELAY_FACTOR: u32 = 4; + +/// Spawns a routine which checks the validity of any optimistically imported transition blocks +/// +/// This routine will run once per epoch, at `epoch_duration / EPOCH_DELAY_FACTOR` after +/// the start of each epoch. +/// +/// The service will not be started if there is no `execution_layer` on the `chain`. +pub fn start_otb_verification_service( + executor: TaskExecutor, + chain: Arc>, +) { + // Avoid spawning the service if there's no EL, it'll just error anyway. + if chain.execution_layer.is_some() { + executor.spawn( + async move { otb_verification_service(chain).await }, + "otb_verification_service", + ); + } +} + +pub fn load_optimistic_transition_blocks( + chain: &BeaconChain, +) -> Result, StoreError> { + process_results( + chain.store.hot_db.iter_column::(OTBColumn), + |iter| { + iter.map(|(_, bytes)| OptimisticTransitionBlock::from_store_bytes(&bytes)) + .collect() + }, + )? +} + +#[derive(Debug)] +pub enum Error { + ForkChoice(String), + BeaconChain(BeaconChainError), + StoreError(StoreError), + NoBlockFound(OptimisticTransitionBlock), +} + +pub async fn validate_optimistic_transition_blocks( + chain: &Arc>, + otbs: Vec, +) -> Result<(), Error> { + let finalized_slot = chain + .canonical_head + .fork_choice_read_lock() + .get_finalized_block() + .map_err(|e| Error::ForkChoice(format!("{:?}", e)))? + .slot; + + // separate otbs into + // non-canonical + // finalized canonical + // unfinalized canonical + let mut non_canonical_otbs = vec![]; + let (finalized_canonical_otbs, unfinalized_canonical_otbs) = process_results( + otbs.into_iter().map(|otb| { + otb.is_canonical(chain) + .map(|is_canonical| (otb, is_canonical)) + }), + |pair_iter| { + pair_iter + .filter_map(|(otb, is_canonical)| { + if is_canonical { + Some(otb) + } else { + non_canonical_otbs.push(otb); + None + } + }) + .partition::, _>(|otb| *otb.slot() <= finalized_slot) + }, + ) + .map_err(Error::BeaconChain)?; + + // remove non-canonical blocks that conflict with finalized checkpoint from the database + for otb in non_canonical_otbs { + if *otb.slot() <= finalized_slot { + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + } + } + + // ensure finalized canonical otb are valid, otherwise kill client + for otb in finalized_canonical_otbs { + match chain.get_block(otb.root()).await { + Ok(Some(block)) => { + match validate_merge_block(chain, block.message(), AllowOptimisticImport::No).await + { + Ok(()) => { + // merge transition block is valid, remove it from OTB + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + info!( + block_root = %otb.root(), + "type" = "finalized", + "Validated merge transition block" + ); + } + // The block was not able to be verified by the EL. Leave the OTB in the + // database since the EL is likely still syncing and may verify the block + // later. + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::UnverifiedNonOptimisticCandidate, + )) => (), + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::InvalidTerminalPoWBlock { .. }, + )) => { + // Finalized Merge Transition Block is Invalid! Kill the Client! + crit!( + msg = "You must use the `--purge-db` flag to clear the database and restart sync. \ + You may be on a hostile network.", + block_hash = ?block.canonical_root(), + "Finalized merge transition block is invalid!" + ); + let mut shutdown_sender = chain.shutdown_sender(); + if let Err(e) = shutdown_sender.try_send(ShutdownReason::Failure( + INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, + )) { + crit!( + error = ?e, + shutdown_reason = INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, + "Failed to shut down client" + ); + } + } + _ => {} + } + } + Ok(None) => return Err(Error::NoBlockFound(otb)), + // Our database has pruned the payload and the payload was unavailable on the EL since + // the EL is still syncing or the payload is non-canonical. + Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => (), + Err(e) => return Err(Error::BeaconChain(e)), + } + } + + // attempt to validate any non-finalized canonical otb blocks + for otb in unfinalized_canonical_otbs { + match chain.get_block(otb.root()).await { + Ok(Some(block)) => { + match validate_merge_block(chain, block.message(), AllowOptimisticImport::No).await + { + Ok(()) => { + // merge transition block is valid, remove it from OTB + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + info!( + block_root = ?otb.root(), + "type" = "not finalized", + "Validated merge transition block" + ); + } + // The block was not able to be verified by the EL. Leave the OTB in the + // database since the EL is likely still syncing and may verify the block + // later. + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::UnverifiedNonOptimisticCandidate, + )) => (), + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::InvalidTerminalPoWBlock { .. }, + )) => { + // Unfinalized Merge Transition Block is Invalid -> Run process_invalid_execution_payload + warn!( + block_root = ?otb.root(), + "Merge transition block invalid" + ); + chain + .process_invalid_execution_payload( + &InvalidationOperation::InvalidateOne { + block_root: *otb.root(), + }, + ) + .await + .map_err(|e| { + warn!( + error = ?e, + location = "process_invalid_execution_payload", + "Error checking merge transition block" + ); + Error::BeaconChain(e) + })?; + } + _ => {} + } + } + Ok(None) => return Err(Error::NoBlockFound(otb)), + // Our database has pruned the payload and the payload was unavailable on the EL since + // the EL is still syncing or the payload is non-canonical. + Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => (), + Err(e) => return Err(Error::BeaconChain(e)), + } + } + + Ok(()) +} + +/// Loop until any optimistically imported merge transition blocks have been verified and +/// the merge has been finalized. +async fn otb_verification_service(chain: Arc>) { + let epoch_duration = chain.slot_clock.slot_duration() * T::EthSpec::slots_per_epoch() as u32; + loop { + match chain + .slot_clock + .duration_to_next_epoch(T::EthSpec::slots_per_epoch()) + { + Some(duration) => { + let additional_delay = epoch_duration / EPOCH_DELAY_FACTOR; + sleep(duration + additional_delay).await; + + debug!("OTB verification service firing"); + + if !is_merge_transition_complete( + &chain.canonical_head.cached_head().snapshot.beacon_state, + ) { + // We are pre-merge. Nothing to do yet. + continue; + } + + // load all optimistically imported transition blocks from the database + match load_optimistic_transition_blocks(chain.as_ref()) { + Ok(otbs) => { + if otbs.is_empty() { + if chain + .canonical_head + .fork_choice_read_lock() + .get_finalized_block() + .map_or(false, |block| { + block.execution_status.is_execution_enabled() + }) + { + // there are no optimistic blocks in the database, we can exit + // the service since the merge transition is finalized and we'll + // never see another transition block + break; + } else { + debug!( + info = "waiting for the merge transition to finalize", + "No optimistic transition blocks" + ) + } + } + if let Err(e) = validate_optimistic_transition_blocks(&chain, otbs).await { + warn!( + error = ?e, + "Error while validating optimistic transition blocks" + ); + } + } + Err(e) => { + error!( + error = ?e, + "Error loading optimistic transition blocks" + ); + } + }; + } + None => { + error!("Failed to read slot clock"); + // If we can't read the slot clock, just wait another slot. + sleep(chain.slot_clock.slot_duration()).await; + } + }; + } + debug!( + msg = "shutting down OTB verification service", + "No optimistic transition blocks in database" + ); +} diff --git a/beacon_node/beacon_chain/src/pre_finalization_cache.rs b/beacon_node/beacon_chain/src/pre_finalization_cache.rs index 22b76e026c..5bd45dc59f 100644 --- a/beacon_node/beacon_chain/src/pre_finalization_cache.rs +++ b/beacon_node/beacon_chain/src/pre_finalization_cache.rs @@ -2,9 +2,9 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; use itertools::process_results; use lru::LruCache; use parking_lot::Mutex; -use slog::debug; use std::num::NonZeroUsize; use std::time::Duration; +use tracing::debug; use types::non_zero_usize::new_non_zero_usize; use types::Hash256; @@ -87,10 +87,7 @@ impl BeaconChain { // blocks have been flushed out. Solving this issue isn't as simple as hooking the // beacon processor's functions that handle failed blocks because we need the block root // and it has been erased from the `BlockError` by that point. - debug!( - self.log, - "Pre-finalization lookup cache is full"; - ); + debug!("Pre-finalization lookup cache is full"); } Ok(false) } diff --git a/beacon_node/beacon_chain/src/proposer_prep_service.rs b/beacon_node/beacon_chain/src/proposer_prep_service.rs index 140a9659fc..14f7414abc 100644 --- a/beacon_node/beacon_chain/src/proposer_prep_service.rs +++ b/beacon_node/beacon_chain/src/proposer_prep_service.rs @@ -1,9 +1,9 @@ use crate::{BeaconChain, BeaconChainTypes}; -use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::time::sleep; +use tracing::{debug, error}; /// Spawns a routine which ensures the EL is provided advance notice of any block producers. /// @@ -38,10 +38,7 @@ async fn proposer_prep_service( slot_duration.saturating_sub(chain.config.prepare_payload_lookahead); sleep(duration + additional_delay).await; - debug!( - chain.log, - "Proposer prepare routine firing"; - ); + debug!("Proposer prepare routine firing"); let inner_chain = chain.clone(); executor.spawn( @@ -50,20 +47,19 @@ async fn proposer_prep_service( if let Err(e) = inner_chain.prepare_beacon_proposer(current_slot).await { error!( - inner_chain.log, - "Proposer prepare routine failed"; - "error" => ?e + error = ?e, + "Proposer prepare routine failed" ); } } else { - debug!(inner_chain.log, "No slot for proposer prepare routine"); + debug!("No slot for proposer prepare routine"); } }, "proposer_prep_update", ); } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 9504901229..ccfae1b182 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -4,7 +4,6 @@ mod migration_schema_v21; mod migration_schema_v22; use crate::beacon_chain::BeaconChainTypes; -use slog::Logger; use std::sync::Arc; use store::hot_cold_store::{HotColdDB, HotColdDBError}; use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION}; @@ -17,7 +16,6 @@ pub fn migrate_schema( genesis_state_root: Option, from: SchemaVersion, to: SchemaVersion, - log: Logger, ) -> Result<(), StoreError> { match (from, to) { // Migrating from the current schema version to itself is always OK, a no-op. @@ -25,39 +23,39 @@ pub fn migrate_schema( // Upgrade across multiple versions by recursively migrating one step at a time. (_, _) if from.as_u64() + 1 < to.as_u64() => { let next = SchemaVersion(from.as_u64() + 1); - migrate_schema::(db.clone(), genesis_state_root, from, next, log.clone())?; - migrate_schema::(db, genesis_state_root, next, to, log) + migrate_schema::(db.clone(), genesis_state_root, from, next)?; + migrate_schema::(db, genesis_state_root, next, to) } // Downgrade across multiple versions by recursively migrating one step at a time. (_, _) if to.as_u64() + 1 < from.as_u64() => { let next = SchemaVersion(from.as_u64() - 1); - migrate_schema::(db.clone(), genesis_state_root, from, next, log.clone())?; - migrate_schema::(db, genesis_state_root, next, to, log) + migrate_schema::(db.clone(), genesis_state_root, from, next)?; + migrate_schema::(db, genesis_state_root, next, to) } // // Migrations from before SchemaVersion(19) are deprecated. // (SchemaVersion(19), SchemaVersion(20)) => { - let ops = migration_schema_v20::upgrade_to_v20::(db.clone(), log)?; + let ops = migration_schema_v20::upgrade_to_v20::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(20), SchemaVersion(19)) => { - let ops = migration_schema_v20::downgrade_from_v20::(db.clone(), log)?; + let ops = migration_schema_v20::downgrade_from_v20::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(20), SchemaVersion(21)) => { - let ops = migration_schema_v21::upgrade_to_v21::(db.clone(), log)?; + let ops = migration_schema_v21::upgrade_to_v21::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(21), SchemaVersion(20)) => { - let ops = migration_schema_v21::downgrade_from_v21::(db.clone(), log)?; + let ops = migration_schema_v21::downgrade_from_v21::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(21), SchemaVersion(22)) => { // This migration needs to sync data between hot and cold DBs. The schema version is // bumped inside the upgrade_to_v22 fn - migration_schema_v22::upgrade_to_v22::(db.clone(), genesis_state_root, log) + migration_schema_v22::upgrade_to_v22::(db.clone(), genesis_state_root) } // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs index d556d5988d..13fde349f5 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs @@ -2,16 +2,15 @@ use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY}; use operation_pool::{ PersistedOperationPool, PersistedOperationPoolV15, PersistedOperationPoolV20, }; -use slog::{debug, info, Logger}; use std::sync::Arc; use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; +use tracing::{debug, info}; use types::Attestation; pub fn upgrade_to_v20( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Upgrading from v19 to v20"); + info!("Upgrading from v19 to v20"); // Load a V15 op pool and transform it to V20. let Some(PersistedOperationPoolV15:: { @@ -24,7 +23,7 @@ pub fn upgrade_to_v20( capella_bls_change_broadcast_indices, }) = db.get_item(&OP_POOL_DB_KEY)? else { - debug!(log, "Nothing to do, no operation pool stored"); + debug!("Nothing to do, no operation pool stored"); return Ok(vec![]); }; @@ -52,9 +51,8 @@ pub fn upgrade_to_v20( pub fn downgrade_from_v20( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Downgrading from v20 to v19"); + info!("Downgrading from v20 to v19"); // Load a V20 op pool and transform it to V15. let Some(PersistedOperationPoolV20:: { @@ -67,7 +65,7 @@ pub fn downgrade_from_v20( capella_bls_change_broadcast_indices, }) = db.get_item(&OP_POOL_DB_KEY)? else { - debug!(log, "Nothing to do, no operation pool stored"); + debug!("Nothing to do, no operation pool stored"); return Ok(vec![]); }; @@ -77,7 +75,10 @@ pub fn downgrade_from_v20( if let Attestation::Base(attestation) = attestation.into() { Some((attestation, indices)) } else { - info!(log, "Dropping attestation during downgrade"; "reason" => "not a base attestation"); + info!( + reason = "not a base attestation", + "Dropping attestation during downgrade" + ); None } }) @@ -88,7 +89,10 @@ pub fn downgrade_from_v20( .filter_map(|slashing| match slashing.try_into() { Ok(slashing) => Some(slashing), Err(_) => { - info!(log, "Dropping attester slashing during downgrade"; "reason" => "not a base attester slashing"); + info!( + reason = "not a base attester slashing", + "Dropping attester slashing during downgrade" + ); None } }) diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs index f02f5ee6f3..d73660cf3c 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs @@ -1,18 +1,17 @@ use crate::beacon_chain::BeaconChainTypes; use crate::validator_pubkey_cache::DatabasePubkey; -use slog::{info, Logger}; use ssz::{Decode, Encode}; use std::sync::Arc; use store::{DBColumn, Error, HotColdDB, KeyValueStore, KeyValueStoreOp, StoreItem}; +use tracing::info; use types::{Hash256, PublicKey}; const LOG_EVERY: usize = 200_000; pub fn upgrade_to_v21( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Upgrading from v20 to v21"); + info!("Upgrading from v20 to v21"); let mut ops = vec![]; @@ -29,22 +28,20 @@ pub fn upgrade_to_v21( if i > 0 && i % LOG_EVERY == 0 { info!( - log, - "Public key decompression in progress"; - "keys_decompressed" => i + keys_decompressed = i, + "Public key decompression in progress" ); } } - info!(log, "Public key decompression complete"); + info!("Public key decompression complete"); Ok(ops) } pub fn downgrade_from_v21( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Downgrading from v21 to v20"); + info!("Downgrading from v21 to v20"); let mut ops = vec![]; @@ -67,15 +64,11 @@ pub fn downgrade_from_v21( )); if i > 0 && i % LOG_EVERY == 0 { - info!( - log, - "Public key compression in progress"; - "keys_compressed" => i - ); + info!(keys_compressed = i, "Public key compression in progress"); } } - info!(log, "Public key compression complete"); + info!("Public key compression complete"); Ok(ops) } diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs index 982c3ded46..0b64fdbe08 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs @@ -1,5 +1,4 @@ use crate::beacon_chain::BeaconChainTypes; -use slog::{info, Logger}; use std::sync::Arc; use store::chunked_iter::ChunkedVectorIter; use store::{ @@ -10,6 +9,7 @@ use store::{ partial_beacon_state::PartialBeaconState, AnchorInfo, DBColumn, Error, HotColdDB, KeyValueStore, KeyValueStoreOp, }; +use tracing::info; use types::{BeaconState, Hash256, Slot}; const LOG_EVERY: usize = 200_000; @@ -40,9 +40,8 @@ fn load_old_schema_frozen_state( pub fn upgrade_to_v22( db: Arc>, genesis_state_root: Option, - log: Logger, ) -> Result<(), Error> { - info!(log, "Upgrading from v21 to v22"); + info!("Upgrading from v21 to v22"); let old_anchor = db.get_anchor_info(); @@ -71,9 +70,8 @@ pub fn upgrade_to_v22( // this write. if split_slot > 0 { info!( - log, - "Re-storing genesis state"; - "state_root" => ?genesis_state_root, + state_root = ?genesis_state_root, + "Re-storing genesis state" ); db.store_cold_state(&genesis_state_root, &genesis_state, &mut cold_ops)?; } @@ -87,7 +85,6 @@ pub fn upgrade_to_v22( oldest_block_slot, split_slot, &mut cold_ops, - &log, )?; // Commit this first batch of non-destructive cold database ops. @@ -107,14 +104,13 @@ pub fn upgrade_to_v22( db.store_schema_version_atomically(SchemaVersion(22), hot_ops)?; // Finally, clean up the old-format data from the freezer database. - delete_old_schema_freezer_data::(&db, &log)?; + delete_old_schema_freezer_data::(&db)?; Ok(()) } pub fn delete_old_schema_freezer_data( db: &Arc>, - log: &Logger, ) -> Result<(), Error> { let mut cold_ops = vec![]; @@ -140,11 +136,7 @@ pub fn delete_old_schema_freezer_data( } let delete_ops = cold_ops.len(); - info!( - log, - "Deleting historic states"; - "delete_ops" => delete_ops, - ); + info!(delete_ops, "Deleting historic states"); db.cold_db.do_atomically(cold_ops)?; // In order to reclaim space, we need to compact the freezer DB as well. @@ -159,13 +151,11 @@ pub fn write_new_schema_block_roots( oldest_block_slot: Slot, split_slot: Slot, cold_ops: &mut Vec, - log: &Logger, ) -> Result<(), Error> { info!( - log, - "Starting beacon block root migration"; - "oldest_block_slot" => oldest_block_slot, - "genesis_block_root" => ?genesis_block_root, + %oldest_block_slot, + ?genesis_block_root, + "Starting beacon block root migration" ); // Store the genesis block root if it would otherwise not be stored. @@ -196,9 +186,8 @@ pub fn write_new_schema_block_roots( if i > 0 && i % LOG_EVERY == 0 { info!( - log, - "Beacon block root migration in progress"; - "roots_migrated" => i + roots_migrated = i, + "Beacon block root migration in progress" ); } } diff --git a/beacon_node/beacon_chain/src/shuffling_cache.rs b/beacon_node/beacon_chain/src/shuffling_cache.rs index 67ca72254b..1aa23c28fc 100644 --- a/beacon_node/beacon_chain/src/shuffling_cache.rs +++ b/beacon_node/beacon_chain/src/shuffling_cache.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use std::sync::Arc; use itertools::Itertools; -use slog::{debug, Logger}; - use oneshot_broadcast::{oneshot, Receiver, Sender}; +use tracing::debug; use types::{ beacon_state::CommitteeCache, AttestationShufflingId, BeaconState, Epoch, EthSpec, Hash256, RelativeEpoch, @@ -61,16 +60,14 @@ pub struct ShufflingCache { cache: HashMap, cache_size: usize, head_shuffling_ids: BlockShufflingIds, - logger: Logger, } impl ShufflingCache { - pub fn new(cache_size: usize, head_shuffling_ids: BlockShufflingIds, logger: Logger) -> Self { + pub fn new(cache_size: usize, head_shuffling_ids: BlockShufflingIds) -> Self { Self { cache: HashMap::new(), cache_size, head_shuffling_ids, - logger, } } @@ -138,7 +135,7 @@ impl ShufflingCache { .get(&key) // Replace the committee if it's not present or if it's a promise. A bird in the hand is // worth two in the promise-bush! - .map_or(true, CacheItem::is_promise) + .is_none_or(CacheItem::is_promise) { self.insert_cache_item( key, @@ -179,10 +176,9 @@ impl ShufflingCache { for shuffling_id in shuffling_ids_to_prune.iter() { debug!( - self.logger, - "Removing old shuffling from cache"; - "shuffling_epoch" => shuffling_id.shuffling_epoch, - "shuffling_decision_block" => ?shuffling_id.shuffling_decision_block + shuffling_epoch = %shuffling_id.shuffling_epoch, + shuffling_decision_block = ?shuffling_id.shuffling_decision_block, + "Removing old shuffling from cache" ); self.cache.remove(shuffling_id); } @@ -294,10 +290,10 @@ impl BlockShufflingIds { #[cfg(not(debug_assertions))] #[cfg(test)] mod test { - use task_executor::test_utils::test_logger; use types::*; use crate::test_utils::EphemeralHarnessType; + use logging::create_test_tracing_subscriber; use super::*; @@ -308,6 +304,8 @@ mod test { // Creates a new shuffling cache for testing fn new_shuffling_cache() -> ShufflingCache { + create_test_tracing_subscriber(); + let current_epoch = 8; let head_shuffling_ids = BlockShufflingIds { current: shuffling_id(current_epoch), @@ -315,8 +313,8 @@ mod test { previous: Some(shuffling_id(current_epoch - 1)), block_root: Hash256::from_low_u64_le(0), }; - let logger = test_logger(); - ShufflingCache::new(TEST_CACHE_SIZE, head_shuffling_ids, logger) + + ShufflingCache::new(TEST_CACHE_SIZE, head_shuffling_ids) } /// Returns two different committee caches for testing. diff --git a/beacon_node/beacon_chain/src/state_advance_timer.rs b/beacon_node/beacon_chain/src/state_advance_timer.rs index 1d8bfff216..f4216ef76d 100644 --- a/beacon_node/beacon_chain/src/state_advance_timer.rs +++ b/beacon_node/beacon_chain/src/state_advance_timer.rs @@ -17,7 +17,6 @@ use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOC use crate::{ chain_config::FORK_CHOICE_LOOKAHEAD_FACTOR, BeaconChain, BeaconChainError, BeaconChainTypes, }; -use slog::{debug, error, warn, Logger}; use slot_clock::SlotClock; use state_processing::per_slot_processing; use std::sync::{ @@ -27,6 +26,7 @@ use std::sync::{ use store::KeyValueStore; use task_executor::TaskExecutor; use tokio::time::{sleep, sleep_until, Instant}; +use tracing::{debug, error, warn}; use types::{AttestationShufflingId, BeaconStateError, EthSpec, Hash256, RelativeEpoch, Slot}; /// If the head slot is more than `MAX_ADVANCE_DISTANCE` from the current slot, then don't perform @@ -107,10 +107,9 @@ impl Lock { pub fn spawn_state_advance_timer( executor: TaskExecutor, beacon_chain: Arc>, - log: Logger, ) { executor.spawn( - state_advance_timer(executor.clone(), beacon_chain, log), + state_advance_timer(executor.clone(), beacon_chain), "state_advance_timer", ); } @@ -119,7 +118,6 @@ pub fn spawn_state_advance_timer( async fn state_advance_timer( executor: TaskExecutor, beacon_chain: Arc>, - log: Logger, ) { let is_running = Lock::new(); let slot_clock = &beacon_chain.slot_clock; @@ -127,7 +125,7 @@ async fn state_advance_timer( loop { let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; continue; @@ -161,9 +159,8 @@ async fn state_advance_timer( Ok(slot) => slot, Err(e) => { warn!( - log, - "Unable to determine slot in state advance timer"; - "error" => ?e + error = ?e, + "Unable to determine slot in state advance timer" ); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; @@ -173,37 +170,27 @@ async fn state_advance_timer( // Only spawn the state advance task if the lock was previously free. if !is_running.lock() { - let log = log.clone(); let beacon_chain = beacon_chain.clone(); let is_running = is_running.clone(); executor.spawn_blocking( move || { - match advance_head(&beacon_chain, &log) { + match advance_head(&beacon_chain) { Ok(()) => (), Err(Error::BeaconChain(e)) => error!( - log, - "Failed to advance head state"; - "error" => ?e - ), - Err(Error::StateAlreadyAdvanced { block_root }) => debug!( - log, - "State already advanced on slot"; - "block_root" => ?block_root + error = ?e, + "Failed to advance head state" ), + Err(Error::StateAlreadyAdvanced { block_root }) => { + debug!(?block_root, "State already advanced on slot") + } Err(Error::MaxDistanceExceeded { current_slot, head_slot, - }) => debug!( - log, - "Refused to advance head state"; - "head_slot" => head_slot, - "current_slot" => current_slot, - ), + }) => debug!(%head_slot, %current_slot, "Refused to advance head state"), other => warn!( - log, - "Did not advance head state"; - "reason" => ?other + reason = ?other, + "Did not advance head state" ), }; @@ -214,9 +201,8 @@ async fn state_advance_timer( ); } else { warn!( - log, - "State advance routine overloaded"; - "msg" => "system resources may be overloaded" + msg = "system resources may be overloaded", + "State advance routine overloaded" ) } @@ -225,7 +211,6 @@ async fn state_advance_timer( // Wait for the fork choice instant (which may already be past). sleep_until(fork_choice_instant).await; - let log = log.clone(); let beacon_chain = beacon_chain.clone(); let next_slot = current_slot + 1; executor.spawn( @@ -245,10 +230,9 @@ async fn state_advance_timer( .await .unwrap_or_else(|e| { warn!( - log, - "Unable to prepare proposer with lookahead"; - "error" => ?e, - "slot" => next_slot, + error = ?e, + slot = %next_slot, + "Unable to prepare proposer with lookahead" ); None }); @@ -261,10 +245,9 @@ async fn state_advance_timer( if let Some(tx) = &beacon_chain.fork_choice_signal_tx { if let Err(e) = tx.notify_fork_choice_complete(next_slot) { warn!( - log, - "Error signalling fork choice waiter"; - "error" => ?e, - "slot" => next_slot, + error = ?e, + slot = %next_slot, + "Error signalling fork choice waiter" ); } } @@ -282,10 +265,7 @@ async fn state_advance_timer( /// slot then placed in the `state_cache` to be used for block verification. /// /// See the module-level documentation for rationale. -fn advance_head( - beacon_chain: &Arc>, - log: &Logger, -) -> Result<(), Error> { +fn advance_head(beacon_chain: &Arc>) -> Result<(), Error> { let current_slot = beacon_chain.slot()?; // These brackets ensure that the `head_slot` value is dropped before we run fork choice and @@ -344,10 +324,9 @@ fn advance_head( // Expose Prometheus metrics. if let Err(e) = summary.observe_metrics() { error!( - log, - "Failed to observe epoch summary metrics"; - "src" => "state_advance_timer", - "error" => ?e + src = "state_advance_timer", + error = ?e, + "Failed to observe epoch summary metrics" ); } @@ -362,20 +341,18 @@ fn advance_head( .process_validator_statuses(state.current_epoch(), &summary, &beacon_chain.spec) { error!( - log, - "Unable to process validator statuses"; - "error" => ?e + error = ?e, + "Unable to process validator statuses" ); } } } debug!( - log, - "Advanced head state one slot"; - "head_block_root" => ?head_block_root, - "state_slot" => state.slot(), - "current_slot" => current_slot, + ?head_block_root, + state_slot = %state.slot(), + %current_slot, + "Advanced head state one slot" ); // Build the current epoch cache, to prepare to compute proposer duties. @@ -420,12 +397,11 @@ fn advance_head( .insert_committee_cache(shuffling_id.clone(), committee_cache); debug!( - log, - "Primed proposer and attester caches"; - "head_block_root" => ?head_block_root, - "next_epoch_shuffling_root" => ?shuffling_id.shuffling_decision_block, - "state_epoch" => state.current_epoch(), - "current_epoch" => current_slot.epoch(T::EthSpec::slots_per_epoch()), + ?head_block_root, + next_epoch_shuffling_root = ?shuffling_id.shuffling_decision_block, + state_epoch = %state.current_epoch(), + current_epoch = %current_slot.epoch(T::EthSpec::slots_per_epoch()), + "Primed proposer and attester caches" ); } @@ -447,13 +423,12 @@ fn advance_head( let current_slot = beacon_chain.slot()?; if starting_slot < current_slot { warn!( - log, - "State advance too slow"; - "head_block_root" => %head_block_root, - "advanced_slot" => final_slot, - "current_slot" => current_slot, - "starting_slot" => starting_slot, - "msg" => "system resources may be overloaded", + %head_block_root, + advanced_slot = %final_slot, + %current_slot, + %starting_slot, + msg = "system resources may be overloaded", + "State advance too slow" ); } @@ -473,11 +448,10 @@ fn advance_head( drop(txn_lock); debug!( - log, - "Completed state advance"; - "head_block_root" => ?head_block_root, - "advanced_slot" => final_slot, - "initial_slot" => initial_slot, + ?head_block_root, + advanced_slot = %final_slot, + %initial_slot, + "Completed state advance" ); Ok(()) diff --git a/beacon_node/beacon_chain/src/sync_committee_rewards.rs b/beacon_node/beacon_chain/src/sync_committee_rewards.rs index 9b35cff943..cf4cf1fff2 100644 --- a/beacon_node/beacon_chain/src/sync_committee_rewards.rs +++ b/beacon_node/beacon_chain/src/sync_committee_rewards.rs @@ -1,11 +1,11 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; -use eth2::lighthouse::SyncCommitteeReward; +use eth2::types::SyncCommitteeReward; use safe_arith::SafeArith; -use slog::error; use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards; use std::collections::HashMap; use store::RelativeEpoch; +use tracing::error; use types::{AbstractExecPayload, BeaconBlockRef, BeaconState}; impl BeaconChain { @@ -31,8 +31,8 @@ impl BeaconChain { let (participant_reward_value, proposer_reward_per_bit) = compute_sync_aggregate_rewards(state, spec).map_err(|e| { error!( - self.log, "Error calculating sync aggregate rewards"; - "error" => ?e + error = ?e, + "Error calculating sync aggregate rewards" ); BeaconChainError::SyncCommitteeRewardsSyncError })?; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 8c9e3929f6..beff95eb77 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -35,6 +35,7 @@ pub use genesis::{InteropGenesisBuilder, DEFAULT_ETH1_BLOCK_HASH}; use int_to_bytes::int_to_bytes32; use kzg::trusted_setup::get_trusted_setup; use kzg::{Kzg, TrustedSetup}; +use logging::create_test_tracing_subscriber; use merkle_proof::MerkleTree; use operation_pool::ReceivedPreCapella; use parking_lot::Mutex; @@ -44,17 +45,12 @@ use rand::Rng; use rand::SeedableRng; use rayon::prelude::*; use sensitive_url::SensitiveUrl; -use slog::{o, Drain, Logger}; -use slog_async::Async; -use slog_term::{FullFormat, PlainSyncDecorator, TermDecorator}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::per_block_processing::compute_timestamp_at_slot; use state_processing::state_advance::complete_state_advance; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io::BufWriter; use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, LazyLock}; @@ -235,7 +231,6 @@ pub struct Builder { genesis_state_builder: Option>, import_all_data_columns: bool, runtime: TestRuntime, - log: Logger, } impl Builder> { @@ -247,12 +242,8 @@ impl Builder> { .expect("cannot build without validator keypairs"); let store = Arc::new( - HotColdDB::open_ephemeral( - self.store_config.clone().unwrap_or_default(), - spec.clone(), - self.log.clone(), - ) - .unwrap(), + HotColdDB::open_ephemeral(self.store_config.clone().unwrap_or_default(), spec.clone()) + .unwrap(), ); let genesis_state_builder = self.genesis_state_builder.take().unwrap_or_else(|| { // Set alternating withdrawal credentials if no builder is specified. @@ -283,12 +274,8 @@ impl Builder> { let spec = self.spec.as_ref().expect("cannot build without spec"); let store = Arc::new( - HotColdDB::open_ephemeral( - self.store_config.clone().unwrap_or_default(), - spec.clone(), - self.log.clone(), - ) - .unwrap(), + HotColdDB::open_ephemeral(self.store_config.clone().unwrap_or_default(), spec.clone()) + .unwrap(), ); let mutator = move |builder: BeaconChainBuilder<_>| { builder @@ -372,7 +359,6 @@ where { pub fn new(eth_spec_instance: E) -> Self { let runtime = TestRuntime::default(); - let log = runtime.log.clone(); Self { eth_spec_instance, @@ -391,7 +377,6 @@ where genesis_state_builder: None, import_all_data_columns: false, runtime, - log, } } @@ -439,12 +424,6 @@ where self } - pub fn logger(mut self, log: Logger) -> Self { - self.log = log.clone(); - self.runtime.set_logger(log); - self - } - /// This mutator will be run before the `store_mutator`. pub fn initial_mutator(mut self, mutator: BoxedMutator) -> Self { assert!( @@ -501,12 +480,8 @@ where suggested_fee_recipient: Some(Address::repeat_byte(42)), ..Default::default() }; - let execution_layer = ExecutionLayer::from_config( - config, - self.runtime.task_executor.clone(), - self.log.clone(), - ) - .unwrap(); + let execution_layer = + ExecutionLayer::from_config(config, self.runtime.task_executor.clone()).unwrap(); self.execution_layer = Some(execution_layer); self @@ -586,7 +561,6 @@ where pub fn build(self) -> BeaconChainHarness> { let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1); - let log = self.log; let spec = self.spec.expect("cannot build without spec"); let seconds_per_slot = spec.seconds_per_slot; let validator_keypairs = self @@ -599,7 +573,6 @@ where let chain_config = self.chain_config.unwrap_or_default(); let mut builder = BeaconChainBuilder::new(self.eth_spec_instance, kzg.clone()) - .logger(log.clone()) .custom_spec(spec.clone()) .store(self.store.expect("cannot build without store")) .store_migrator_config( @@ -614,10 +587,7 @@ where .shutdown_sender(shutdown_tx) .chain_config(chain_config) .import_all_data_columns(self.import_all_data_columns) - .event_handler(Some(ServerSentEventHandler::new_with_capacity( - log.clone(), - 5, - ))) + .event_handler(Some(ServerSentEventHandler::new_with_capacity(5))) .validator_monitor_config(validator_monitor_config); builder = if let Some(mutator) = self.initial_mutator { @@ -645,6 +615,12 @@ where let chain = builder.build().expect("should build"); + let sampling_column_count = if self.import_all_data_columns { + chain.spec.number_of_custody_groups as usize + } else { + chain.spec.custody_requirement as usize + }; + BeaconChainHarness { spec: chain.spec.clone(), chain: Arc::new(chain), @@ -655,6 +631,7 @@ where mock_execution_layer: self.mock_execution_layer, mock_builder: None, rng: make_rng(), + sampling_column_count, } } } @@ -711,6 +688,7 @@ pub struct BeaconChainHarness { pub mock_execution_layer: Option>, pub mock_builder: Option>>, + pub sampling_column_count: usize, pub rng: Mutex, } @@ -737,13 +715,10 @@ where Cold: ItemStore, { pub fn builder(eth_spec_instance: E) -> Builder> { + create_test_tracing_subscriber(); Builder::new(eth_spec_instance) } - pub fn logger(&self) -> &slog::Logger { - &self.chain.log - } - pub fn execution_block_generator(&self) -> RwLockWriteGuard<'_, ExecutionBlockGenerator> { self.mock_execution_layer .as_ref() @@ -779,6 +754,7 @@ where SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap(), None, None, + false, ) .unwrap(); @@ -814,6 +790,10 @@ where (0..self.validator_keypairs.len()).collect() } + pub fn get_sampling_column_count(&self) -> usize { + self.sampling_column_count + } + pub fn slots_per_epoch(&self) -> u64 { E::slots_per_epoch() } @@ -889,7 +869,7 @@ where pub fn get_hot_state(&self, state_hash: BeaconStateHash) -> Option> { self.chain .store - .load_hot_state(&state_hash.into()) + .load_hot_state(&state_hash.into(), true) .unwrap() .map(|(state, _)| state) } @@ -2374,8 +2354,14 @@ where .into_iter() .map(CustodyDataColumn::from_asserted_custody) .collect::>(); - RpcBlock::new_with_custody_columns(Some(block_root), block, custody_columns, &self.spec) - .unwrap() + RpcBlock::new_with_custody_columns( + Some(block_root), + block, + custody_columns, + self.get_sampling_column_count(), + &self.spec, + ) + .unwrap() } else { let blobs = self.chain.get_blobs(&block_root).unwrap().blobs(); RpcBlock::new(Some(block_root), block, blobs).unwrap() @@ -2390,10 +2376,7 @@ where blob_items: Option<(KzgProofs, BlobsList)>, ) -> Result, BlockError> { Ok(if self.spec.is_peer_das_enabled_for_epoch(block.epoch()) { - let sampling_column_count = self - .chain - .data_availability_checker - .get_sampling_column_count(); + let sampling_column_count = self.get_sampling_column_count(); if blob_items.is_some_and(|(_, blobs)| !blobs.is_empty()) { // Note: this method ignores the actual custody columns and just take the first @@ -2404,7 +2387,13 @@ where .take(sampling_column_count) .map(CustodyDataColumn::from_asserted_custody) .collect::>(); - RpcBlock::new_with_custody_columns(Some(block_root), block, columns, &self.spec)? + RpcBlock::new_with_custody_columns( + Some(block_root), + block, + columns, + sampling_column_count, + &self.spec, + )? } else { RpcBlock::new_without_blobs(Some(block_root), block) } @@ -2617,7 +2606,6 @@ where return; } - let log = self.logger(); let contributions = self.make_sync_contributions(state, block_root, slot, RelativeSyncCommittee::Current); @@ -2648,7 +2636,6 @@ where slot, &block_root, &sync_aggregate, - log, &self.spec, ); } @@ -2712,16 +2699,16 @@ where let mut block_hash_from_slot: HashMap = HashMap::new(); let mut state_hash_from_slot: HashMap = HashMap::new(); for slot in slots { - let (block_hash, new_state) = self - .add_attested_block_at_slot_with_sync( - *slot, - state, - state_root, - validators, - sync_committee_strategy, - ) - .await - .unwrap(); + // Using a `Box::pin` to reduce the stack size. Clippy was raising a lints. + let (block_hash, new_state) = Box::pin(self.add_attested_block_at_slot_with_sync( + *slot, + state, + state_root, + validators, + sync_committee_strategy, + )) + .await + .unwrap(); state = new_state; @@ -3105,10 +3092,7 @@ where let is_peerdas_enabled = self.chain.spec.is_peer_das_enabled_for_epoch(block.epoch()); if is_peerdas_enabled { let custody_columns = custody_columns_opt.unwrap_or_else(|| { - let sampling_column_count = self - .chain - .data_availability_checker - .get_sampling_column_count() as u64; + let sampling_column_count = self.get_sampling_column_count() as u64; (0..sampling_column_count).collect() }); @@ -3158,58 +3142,6 @@ pub struct MakeAttestationOptions { pub fork: Fork, } -pub enum LoggerType { - Test, - // The logs are output to files for each test. - CI, - // No logs will be printed. - Null, -} - -fn ci_decorator() -> PlainSyncDecorator> { - let log_dir = std::env::var(CI_LOGGER_DIR_ENV_VAR).unwrap_or_else(|e| { - panic!("{CI_LOGGER_DIR_ENV_VAR} env var must be defined when using ci_logger: {e:?}"); - }); - let fork_name = std::env::var(FORK_NAME_ENV_VAR) - .map(|s| format!("{s}_")) - .unwrap_or_default(); - // The current test name can be got via the thread name. - let test_name = std::thread::current() - .name() - .unwrap() - .to_string() - // Colons are not allowed in files that are uploaded to GitHub Artifacts. - .replace("::", "_"); - let log_path = format!("/{log_dir}/{fork_name}{test_name}.log"); - let file = OpenOptions::new() - .create(true) - .append(true) - .open(log_path) - .unwrap(); - let file = BufWriter::new(file); - PlainSyncDecorator::new(file) -} - -pub fn build_log(level: slog::Level, logger_type: LoggerType) -> Logger { - match logger_type { - LoggerType::Test => { - let drain = FullFormat::new(TermDecorator::new().build()).build().fuse(); - let drain = Async::new(drain).chan_size(10_000).build().fuse(); - Logger::root(drain.filter_level(level).fuse(), o!()) - } - LoggerType::CI => { - let drain = FullFormat::new(ci_decorator()).build().fuse(); - let drain = Async::new(drain).chan_size(10_000).build().fuse(); - Logger::root(drain.filter_level(level).fuse(), o!()) - } - LoggerType::Null => { - let drain = FullFormat::new(TermDecorator::new().build()).build().fuse(); - let drain = Async::new(drain).build().fuse(); - Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } -} - pub enum NumBlobs { Random, Number(usize), diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index f8a483c621..16f4e3f143 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -5,9 +5,9 @@ use crate::beacon_proposer_cache::{BeaconProposerCache, TYPICAL_SLOTS_PER_EPOCH}; use crate::metrics; use itertools::Itertools; +use logging::crit; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use smallvec::SmallVec; use state_processing::common::get_attestation_participation_flag_indices; @@ -21,6 +21,7 @@ use std::str::Utf8Error; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use store::AbstractExecPayload; +use tracing::{debug, error, info, instrument, warn}; use types::consts::altair::{ TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, }; @@ -30,7 +31,6 @@ use types::{ IndexedAttestationRef, ProposerSlashing, PublicKeyBytes, SignedAggregateAndProof, SignedContributionAndProof, Slot, SyncCommitteeMessage, VoluntaryExit, }; - /// Used for Prometheus labels. /// /// We've used `total` for this value to align with Nimbus, as per: @@ -401,15 +401,18 @@ pub struct ValidatorMonitor { beacon_proposer_cache: Arc>, // Unaggregated attestations generated by the committee index at each slot. unaggregated_attestations: HashMap>, - log: Logger, _phantom: PhantomData, } impl ValidatorMonitor { + #[instrument(parent = None, + level = "info", + name = "validator_monitor", + skip_all + )] pub fn new( config: ValidatorMonitorConfig, beacon_proposer_cache: Arc>, - log: Logger, ) -> Self { let ValidatorMonitorConfig { auto_register, @@ -425,7 +428,6 @@ impl ValidatorMonitor { missed_blocks: <_>::default(), beacon_proposer_cache, unaggregated_attestations: <_>::default(), - log, _phantom: PhantomData, }; for pubkey in validators { @@ -437,11 +439,23 @@ impl ValidatorMonitor { /// Returns `true` when the validator count is sufficiently low enough to /// emit metrics and logs on a per-validator basis (rather than just an /// aggregated basis). + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn individual_tracking(&self) -> bool { self.validators.len() <= self.individual_tracking_threshold } /// Add some validators to `self` for additional monitoring. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn add_validator_pubkey(&mut self, pubkey: PublicKeyBytes) { let index_opt = self .indices @@ -449,18 +463,22 @@ impl ValidatorMonitor { .find(|(_, candidate_pk)| **candidate_pk == pubkey) .map(|(index, _)| *index); - let log = self.log.clone(); self.validators.entry(pubkey).or_insert_with(|| { info!( - log, - "Started monitoring validator"; - "pubkey" => %pubkey, + %pubkey, + "Started monitoring validator" ); MonitoredValidator::new(pubkey, index_opt) }); } /// Add an unaggregated attestation + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn set_unaggregated_attestation(&mut self, attestation: Attestation) { let unaggregated_attestations = &mut self.unaggregated_attestations; @@ -474,12 +492,24 @@ impl ValidatorMonitor { self.unaggregated_attestations.insert(slot, attestation); } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_unaggregated_attestation(&self, slot: Slot) -> Option<&Attestation> { self.unaggregated_attestations.get(&slot) } /// Reads information from the given `state`. The `state` *must* be valid (i.e, able to be /// imported). + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn process_valid_state( &mut self, current_epoch: Epoch, @@ -592,6 +622,12 @@ impl ValidatorMonitor { } /// Add missed non-finalized blocks for the monitored validators + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn add_validators_missed_blocks(&mut self, state: &BeaconState) { // Define range variables let current_slot = state.slot(); @@ -628,7 +664,7 @@ impl ValidatorMonitor { // the proposer shuffling cache lock when there are lots of missed blocks. if proposers_per_epoch .as_ref() - .map_or(true, |(_, cached_epoch)| *cached_epoch != slot_epoch) + .is_none_or(|(_, cached_epoch)| *cached_epoch != slot_epoch) { proposers_per_epoch = self .get_proposers_by_epoch_from_cache( @@ -661,28 +697,25 @@ impl ValidatorMonitor { ); }); error!( - self.log, - "Validator missed a block"; - "index" => i, - "slot" => slot, - "parent block root" => ?prev_block_root, + index = i, + %slot, + ?prev_block_root, + "Validator missed a block" ); } } } else { warn!( - self.log, - "Missing validator index"; - "info" => "potentially inconsistency in the validator manager", - "index" => i, + info = "potentially inconsistency in the validator manager", + index = i, + "Missing validator index" ) } } else { debug!( - self.log, - "Could not get proposers from cache"; - "epoch" => ?slot_epoch, - "decision_root" => ?shuffling_decision_block, + epoch = ?slot_epoch, + decision_root = ?shuffling_decision_block, + "Could not get proposers from cache" ); } } @@ -691,6 +724,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn get_proposers_by_epoch_from_cache( &mut self, epoch: Epoch, @@ -704,6 +743,12 @@ impl ValidatorMonitor { /// Process the unaggregated attestations generated by the service `attestation_simulator_service` /// and check if the attestation qualifies for a reward matching the flags source/target/head + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn process_unaggregated_attestations(&mut self, state: &BeaconState, spec: &ChainSpec) { let current_slot = state.slot(); @@ -744,27 +789,23 @@ impl ValidatorMonitor { let head_hit = flag_indices.contains(&TIMELY_HEAD_FLAG_INDEX); let target_hit = flag_indices.contains(&TIMELY_TARGET_FLAG_INDEX); let source_hit = flag_indices.contains(&TIMELY_SOURCE_FLAG_INDEX); - register_simulated_attestation( - data, head_hit, target_hit, source_hit, &self.log, - ) + register_simulated_attestation(data, head_hit, target_hit, source_hit) } Err(BeaconStateError::IncorrectAttestationSource) => { - register_simulated_attestation(data, false, false, false, &self.log) + register_simulated_attestation(data, false, false, false) } Err(err) => { error!( - self.log, - "Failed to get attestation participation flag indices"; - "error" => ?err, - "unaggregated_attestation" => ?unaggregated_attestation, + error = ?err, + ?unaggregated_attestation, + "Failed to get attestation participation flag indices" ); } } } else { error!( - self.log, - "Failed to remove unaggregated attestation from the hashmap"; - "slot" => ?slot, + ?slot, + "Failed to remove unaggregated attestation from the hashmap" ); } } @@ -780,6 +821,12 @@ impl ValidatorMonitor { /// /// We allow disabling tracking metrics on an individual validator basis /// since it can result in untenable cardinality with high validator counts. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn aggregatable_metric(&self, individual_id: &str, func: F) { func(TOTAL_LABEL); @@ -788,6 +835,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn process_validator_statuses( &self, epoch: Epoch, @@ -867,13 +920,12 @@ impl ValidatorMonitor { attestation_success.push(id); if self.individual_tracking() { debug!( - self.log, - "Previous epoch attestation success"; - "matched_source" => previous_epoch_matched_source, - "matched_target" => previous_epoch_matched_target, - "matched_head" => previous_epoch_matched_head, - "epoch" => prev_epoch, - "validator" => id, + matched_source = previous_epoch_matched_source, + matched_target = previous_epoch_matched_target, + matched_head = previous_epoch_matched_head, + epoch = %prev_epoch, + validator = id, + "Previous epoch attestation success" ) } } else { @@ -886,10 +938,9 @@ impl ValidatorMonitor { attestation_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Previous epoch attestation missing"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Previous epoch attestation missing" ) } } @@ -912,10 +963,9 @@ impl ValidatorMonitor { head_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Attestation failed to match head"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Attestation failed to match head" ); } } @@ -938,10 +988,9 @@ impl ValidatorMonitor { target_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Attestation failed to match target"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Attestation failed to match target" ); } } @@ -960,12 +1009,11 @@ impl ValidatorMonitor { suboptimal_inclusion.push(id); if self.individual_tracking() { debug!( - self.log, - "Potential sub-optimal inclusion delay"; - "optimal" => spec.min_attestation_inclusion_delay, - "delay" => inclusion_delay, - "epoch" => prev_epoch, - "validator" => id, + optimal = spec.min_attestation_inclusion_delay, + delay = inclusion_delay, + epoch = %prev_epoch, + validator = id, + "Potential sub-optimal inclusion delay" ); } } @@ -1003,12 +1051,11 @@ impl ValidatorMonitor { // logs that can be generated is capped by the size // of the sync committee. info!( - self.log, - "Current epoch sync signatures"; - "included" => summary.sync_signature_block_inclusions, - "expected" => E::slots_per_epoch(), - "epoch" => current_epoch, - "validator" => id, + included = summary.sync_signature_block_inclusions, + expected = E::slots_per_epoch(), + epoch = %current_epoch, + validator = id, + "Current epoch sync signatures" ); } } else if self.individual_tracking() { @@ -1018,10 +1065,9 @@ impl ValidatorMonitor { 0, ); debug!( - self.log, - "Validator isn't part of the current sync committee"; - "epoch" => current_epoch, - "validator" => id, + epoch = %current_epoch, + validator = id, + "Validator isn't part of the current sync committee" ); } } @@ -1032,51 +1078,52 @@ impl ValidatorMonitor { // for all validators managed by the validator monitor. if !attestation_success.is_empty() { info!( - self.log, - "Previous epoch attestation(s) success"; - "epoch" => prev_epoch, - "validators" => ?attestation_success, + epoch = %prev_epoch, + validators = ?attestation_success, + "Previous epoch attestation(s) success" ); } if !attestation_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) missing"; - "epoch" => prev_epoch, - "validators" => ?attestation_miss, + epoch = %prev_epoch, + validators = ?attestation_miss, + "Previous epoch attestation(s) missing" ); } if !head_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) failed to match head"; - "epoch" => prev_epoch, - "validators" => ?head_miss, + epoch = %prev_epoch, + validators = ?head_miss, + "Previous epoch attestation(s) failed to match head" ); } if !target_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) failed to match target"; - "epoch" => prev_epoch, - "validators" => ?target_miss, + epoch = %prev_epoch, + validators = ?target_miss, + "Previous epoch attestation(s) failed to match target" ); } if !suboptimal_inclusion.is_empty() { info!( - self.log, - "Previous epoch attestation(s) had sub-optimal inclusion delay"; - "epoch" => prev_epoch, - "validators" => ?suboptimal_inclusion, + epoch = %prev_epoch, + validators = ?suboptimal_inclusion, + "Previous epoch attestation(s) had sub-optimal inclusion delay" ); } Ok(()) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn get_validator(&self, validator_index: u64) -> Option<&MonitoredValidator> { self.indices .get(&validator_index) @@ -1084,15 +1131,33 @@ impl ValidatorMonitor { } /// Returns the number of validators monitored by `self`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn num_validators(&self) -> usize { self.validators.len() } - // Return the `id`'s of all monitored validators. + /// Return the `id`'s of all monitored validators. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_all_monitored_validators(&self) -> Vec { self.validators.values().map(|val| val.id.clone()).collect() } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_monitored_validator(&self, index: u64) -> Option<&MonitoredValidator> { if let Some(pubkey) = self.indices.get(&index) { self.validators.get(pubkey) @@ -1101,6 +1166,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_monitored_validator_missed_block_count(&self, validator_index: u64) -> u64 { self.missed_blocks .iter() @@ -1108,12 +1179,24 @@ impl ValidatorMonitor { .count() as u64 } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_beacon_proposer_cache(&self) -> Arc> { self.beacon_proposer_cache.clone() } /// If `self.auto_register == true`, add the `validator_index` to `self.monitored_validators`. /// Otherwise, do nothing. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn auto_register_local_validator(&mut self, validator_index: u64) { if !self.auto_register { return; @@ -1122,10 +1205,9 @@ impl ValidatorMonitor { if let Some(pubkey) = self.indices.get(&validator_index) { if !self.validators.contains_key(pubkey) { info!( - self.log, - "Started monitoring validator"; - "pubkey" => %pubkey, - "validator" => %validator_index, + %pubkey, + validator = %validator_index, + "Started monitoring validator" ); self.validators.insert( @@ -1137,6 +1219,12 @@ impl ValidatorMonitor { } /// Process a block received on gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_block( &self, seen_timestamp: Duration, @@ -1148,6 +1236,12 @@ impl ValidatorMonitor { } /// Process a block received on the HTTP API from a local validator. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_block( &self, seen_timestamp: Duration, @@ -1158,6 +1252,12 @@ impl ValidatorMonitor { self.register_beacon_block("api", seen_timestamp, block, block_root, slot_clock) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_beacon_block( &self, src: &str, @@ -1184,13 +1284,12 @@ impl ValidatorMonitor { }); info!( - self.log, - "Block from monitored validator"; - "root" => ?block_root, - "delay" => %delay.as_millis(), - "slot" => %block.slot(), - "src" => src, - "validator" => %id, + ?block_root, + delay = %delay.as_millis(), + slot = %block.slot(), + src, + validator = %id, + "Block from monitored validator" ); validator.with_epoch_summary(epoch, |summary| summary.register_block(delay)); @@ -1198,6 +1297,12 @@ impl ValidatorMonitor { } /// Register an attestation seen on the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_unaggregated_attestation( &self, seen_timestamp: Duration, @@ -1213,6 +1318,12 @@ impl ValidatorMonitor { } /// Register an attestation seen on the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_unaggregated_attestation( &self, seen_timestamp: Duration, @@ -1227,6 +1338,12 @@ impl ValidatorMonitor { ) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_unaggregated_attestation( &self, src: &str, @@ -1261,15 +1378,14 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Unaggregated attestation"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Unaggregated attestation" ); } @@ -1314,6 +1430,12 @@ impl ValidatorMonitor { ) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_aggregated_attestation( &self, src: &str, @@ -1349,15 +1471,14 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Aggregated attestation"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Aggregated attestation" ); } @@ -1396,28 +1517,26 @@ impl ValidatorMonitor { if is_first_inclusion_aggregate { info!( - self.log, - "Attestation included in aggregate"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Attestation included in aggregate" ); } else { // Downgrade to Debug for second and onwards of logging to reduce verbosity debug!( - self.log, - "Attestation included in aggregate"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Attestation included in aggregate" ) }; } @@ -1435,6 +1554,11 @@ impl ValidatorMonitor { /// We use the parent slot instead of block slot to ignore skip slots when calculating inclusion distance. /// /// Note: Blocks that get orphaned will skew the inclusion distance calculation. + #[instrument(parent = None, + level = "info", + name = "validator_monitor", + skip_all + )] pub fn register_attestation_in_block( &self, indexed_attestation: IndexedAttestationRef<'_, E>, @@ -1480,26 +1604,24 @@ impl ValidatorMonitor { if is_first_inclusion_block { info!( - self.log, - "Attestation included in block"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "inclusion_lag" => format!("{} slot(s)", delay), - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + inclusion_lag = format!("{} slot(s)", delay), + %epoch, + slot = %data.slot, + validator = %id, + "Attestation included in block" ); } else { // Downgrade to Debug for second and onwards of logging to reduce verbosity debug!( - self.log, - "Attestation included in block"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "inclusion_lag" => format!("{} slot(s)", delay), - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + inclusion_lag = format!("{} slot(s)", delay), + %epoch, + slot = %data.slot, + validator = %id, + "Attestation included in block" ); } } @@ -1512,6 +1634,12 @@ impl ValidatorMonitor { } /// Register a sync committee message received over gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_sync_committee_message( &self, seen_timestamp: Duration, @@ -1527,6 +1655,12 @@ impl ValidatorMonitor { } /// Register a sync committee message received over the http api. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_sync_committee_message( &self, seen_timestamp: Duration, @@ -1542,6 +1676,12 @@ impl ValidatorMonitor { } /// Register a sync committee message. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_sync_committee_message( &self, src: &str, @@ -1574,14 +1714,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync committee message"; - "head" => %sync_committee_message.beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %sync_committee_message.slot, - "src" => src, - "validator" => %id, + head = %sync_committee_message.beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + slot = %sync_committee_message.slot, + src, + validator = %id, + "Sync committee message" ); } @@ -1592,6 +1731,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution received over gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_sync_committee_contribution( &self, seen_timestamp: Duration, @@ -1609,6 +1754,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution received over the http api. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_sync_committee_contribution( &self, seen_timestamp: Duration, @@ -1626,6 +1777,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_sync_committee_contribution( &self, src: &str, @@ -1662,14 +1819,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync contribution"; - "head" => %beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %slot, - "src" => src, - "validator" => %id, + head = %beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + %slot, + src, + validator = %id, + "Sync contribution" ); } @@ -1691,14 +1847,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync signature included in contribution"; - "head" => %beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %slot, - "src" => src, - "validator" => %id, + head = %beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + %slot, + src, + validator = %id, + "Sync signature included in contribution" ); } @@ -1710,6 +1865,12 @@ impl ValidatorMonitor { } /// Register that the `sync_aggregate` was included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_sync_aggregate_in_block( &self, slot: Slot, @@ -1731,12 +1892,11 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync signature included in block"; - "head" => %beacon_block_root, - "epoch" => %epoch, - "slot" => %slot, - "validator" => %id, + head = %beacon_block_root, + %epoch, + %slot, + validator = %id, + "Sync signature included in block" ); } @@ -1748,20 +1908,44 @@ impl ValidatorMonitor { } /// Register an exit from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("gossip", exit) } /// Register an exit from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("api", exit) } /// Register an exit included in a *valid* beacon block. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("block", exit) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_voluntary_exit(&self, src: &str, exit: &VoluntaryExit) { if let Some(validator) = self.get_validator(exit.validator_index) { let id = &validator.id; @@ -1774,11 +1958,10 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. info!( - self.log, - "Voluntary exit"; - "epoch" => %epoch, - "validator" => %id, - "src" => src, + %epoch, + validator = %id, + src, + "Voluntary exit" ); validator.with_epoch_summary(epoch, |summary| summary.register_exit()); @@ -1786,20 +1969,44 @@ impl ValidatorMonitor { } /// Register a proposer slashing from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("gossip", slashing) } /// Register a proposer slashing from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("api", slashing) } /// Register a proposer slashing included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("block", slashing) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_proposer_slashing(&self, src: &str, slashing: &ProposerSlashing) { let proposer = slashing.signed_header_1.message.proposer_index; let slot = slashing.signed_header_1.message.slot; @@ -1820,13 +2027,12 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. crit!( - self.log, - "Proposer slashing"; - "root_2" => %root_2, - "root_1" => %root_1, - "slot" => %slot, - "validator" => %id, - "src" => src, + %root_2, + %root_1, + %slot, + validator = %id, + src, + "Proposer slashing" ); validator.with_epoch_summary(epoch, |summary| summary.register_proposer_slashing()); @@ -1834,20 +2040,44 @@ impl ValidatorMonitor { } /// Register an attester slashing from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("gossip", slashing) } /// Register an attester slashing from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("api", slashing) } /// Register an attester slashing included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("block", slashing) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_attester_slashing(&self, src: &str, slashing: AttesterSlashingRef<'_, E>) { let data = slashing.attestation_1().data(); let attestation_1_indices: HashSet = slashing @@ -1875,12 +2105,11 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. crit!( - self.log, - "Attester slashing"; - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, - "src" => src, + %epoch, + slot = %data.slot, + validator = %id, + src, + "Attester slashing" ); validator.with_epoch_summary(epoch, |summary| summary.register_attester_slashing()); @@ -1890,6 +2119,12 @@ impl ValidatorMonitor { /// Scrape `self` for metrics. /// /// Should be called whenever Prometheus is scraping Lighthouse. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn scrape_metrics(&self, slot_clock: &S, spec: &ChainSpec) { metrics::set_gauge( &metrics::VALIDATOR_MONITOR_VALIDATORS_TOTAL, @@ -2074,7 +2309,6 @@ fn register_simulated_attestation( head_hit: bool, target_hit: bool, source_hit: bool, - log: &Logger, ) { if head_hit { metrics::inc_counter(&metrics::VALIDATOR_MONITOR_ATTESTATION_SIMULATOR_HEAD_ATTESTER_HIT); @@ -2097,15 +2331,14 @@ fn register_simulated_attestation( } debug!( - log, - "Simulated attestation evaluated"; - "attestation_source" => ?data.source.root, - "attestation_target" => ?data.target.root, - "attestation_head" => ?data.beacon_block_root, - "attestation_slot" => ?data.slot, - "source_hit" => source_hit, - "target_hit" => target_hit, - "head_hit" => head_hit, + attestation_source = ?data.source.root, + attestation_target = ?data.target.root, + attestation_head = ?data.beacon_block_root, + attestation_slot = ?data.slot, + source_hit, + target_hit, + head_hit, + "Simulated attestation evaluated" ); } diff --git a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs index 877c297a3b..39d2c2c2d7 100644 --- a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs +++ b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs @@ -210,7 +210,7 @@ impl DatabasePubkey { mod test { use super::*; use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType}; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use std::sync::Arc; use store::HotColdDB; use types::{EthSpec, Keypair, MainnetEthSpec}; @@ -231,10 +231,8 @@ mod test { } fn get_store() -> BeaconStore { - Arc::new( - HotColdDB::open_ephemeral(<_>::default(), Arc::new(E::default_spec()), test_logger()) - .unwrap(), - ) + create_test_tracing_subscriber(); + Arc::new(HotColdDB::open_ephemeral(<_>::default(), Arc::new(E::default_spec())).unwrap()) } #[allow(clippy::needless_range_loop)] diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index 621475a3ec..d89a8530e1 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -242,7 +242,7 @@ async fn produces_attestations() { .early_attester_cache .add_head_block( block_root, - available_block, + &available_block, proto_block, &state, &chain.spec, @@ -310,7 +310,7 @@ async fn early_attester_cache_old_request() { .early_attester_cache .add_head_block( head.beacon_block_root, - available_block, + &available_block, head_proto_block, &head.beacon_state, &harness.chain.spec, diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index dcc63ddf62..30eec539fc 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -36,6 +36,9 @@ pub const VALIDATOR_COUNT: usize = 256; pub const CAPELLA_FORK_EPOCH: usize = 1; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + /// A cached set of keys. static KEYPAIRS: LazyLock> = LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT)); @@ -1225,7 +1228,11 @@ async fn attestation_that_skips_epochs() { let mut state = harness .chain - .get_state(&earlier_block.state_root(), Some(earlier_slot)) + .get_state( + &earlier_block.state_root(), + Some(earlier_slot), + CACHE_STATE_IN_TESTS, + ) .expect("should not error getting state") .expect("should find state"); @@ -1329,9 +1336,14 @@ async fn attestation_validator_receive_proposer_reward_and_withdrawals() { .await; let current_slot = harness.get_current_slot(); + let mut state = harness .chain - .get_state(&earlier_block.state_root(), Some(earlier_slot)) + .get_state( + &earlier_block.state_root(), + Some(earlier_slot), + CACHE_STATE_IN_TESTS, + ) .expect("should not error getting state") .expect("should find state"); @@ -1399,7 +1411,11 @@ async fn attestation_to_finalized_block() { let mut state = harness .chain - .get_state(&earlier_block.state_root(), Some(earlier_slot)) + .get_state( + &earlier_block.state_root(), + Some(earlier_slot), + CACHE_STATE_IN_TESTS, + ) .expect("should not error getting state") .expect("should find state"); diff --git a/beacon_node/beacon_chain/tests/bellatrix.rs b/beacon_node/beacon_chain/tests/bellatrix.rs index 5080b0890b..3a424e73ba 100644 --- a/beacon_node/beacon_chain/tests/bellatrix.rs +++ b/beacon_node/beacon_chain/tests/bellatrix.rs @@ -50,7 +50,6 @@ async fn merge_with_terminal_block_hash_override() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() @@ -107,7 +106,6 @@ async fn base_altair_bellatrix_with_terminal_block_after_fork() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 2a881b5b0f..3dc46be16e 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -12,7 +12,7 @@ use beacon_chain::{ BeaconSnapshot, BlockError, ChainConfig, ChainSegmentResult, IntoExecutionPendingBlock, InvalidSignature, NotifyExecutionLayer, }; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use slasher::{Config as SlasherConfig, Slasher}; use state_processing::{ common::{attesting_indices_base, attesting_indices_electra}, @@ -30,6 +30,8 @@ type E = MainnetEthSpec; const VALIDATOR_COUNT: usize = 24; const CHAIN_SEGMENT_LENGTH: usize = 64 * 5; const BLOCK_INDICES: &[usize] = &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT_LENGTH - 1]; +// Default custody group count for tests +const CGC: usize = 8; /// A cached set of keys. static KEYPAIRS: LazyLock> = @@ -142,7 +144,8 @@ fn build_rpc_block( RpcBlock::new(None, block, Some(blobs.clone())).unwrap() } Some(DataSidecars::DataColumns(columns)) => { - RpcBlock::new_with_custody_columns(None, block, columns.clone(), spec).unwrap() + RpcBlock::new_with_custody_columns(None, block, columns.clone(), columns.len(), spec) + .unwrap() } None => RpcBlock::new_without_blobs(None, block), } @@ -991,6 +994,7 @@ async fn block_gossip_verification() { let (chain_segment, chain_segment_blobs) = get_chain_segment().await; let block_index = CHAIN_SEGMENT_LENGTH - 2; + let cgc = harness.chain.spec.custody_requirement as usize; harness .chain @@ -1004,7 +1008,7 @@ async fn block_gossip_verification() { { let gossip_verified = harness .chain - .verify_block_for_gossip(snapshot.beacon_block.clone()) + .verify_block_for_gossip(snapshot.beacon_block.clone(), get_cgc(&blobs_opt)) .await .expect("should obtain gossip verified block"); @@ -1046,7 +1050,7 @@ async fn block_gossip_verification() { *block.slot_mut() = expected_block_slot; assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await), BlockError::FutureSlot { present_slot, block_slot, @@ -1080,7 +1084,7 @@ async fn block_gossip_verification() { *block.slot_mut() = expected_finalized_slot; assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await), BlockError::WouldRevertFinalizedSlot { block_slot, finalized_slot, @@ -1110,10 +1114,10 @@ async fn block_gossip_verification() { unwrap_err( harness .chain - .verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block( - block, - junk_signature() - ))) + .verify_block_for_gossip( + Arc::new(SignedBeaconBlock::from_block(block, junk_signature())), + cgc + ) .await ), BlockError::InvalidSignature(InvalidSignature::ProposerSignature) @@ -1138,7 +1142,7 @@ async fn block_gossip_verification() { *block.parent_root_mut() = parent_root; assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await), BlockError::ParentUnknown {parent_root: p} if p == parent_root ), @@ -1164,7 +1168,7 @@ async fn block_gossip_verification() { *block.parent_root_mut() = parent_root; assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await), BlockError::NotFinalizedDescendant { block_parent_root } if block_parent_root == parent_root ), @@ -1201,7 +1205,7 @@ async fn block_gossip_verification() { ); assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone()), cgc).await), BlockError::IncorrectBlockProposer { block, local_shuffling, @@ -1213,7 +1217,7 @@ async fn block_gossip_verification() { // Check to ensure that we registered this is a valid block from this proposer. assert!( matches!( - unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await), + unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone()), cgc).await), BlockError::DuplicateImportStatusUnknown(_), ), "should register any valid signature against the proposer, even if the block failed later verification" @@ -1221,7 +1225,11 @@ async fn block_gossip_verification() { let block = chain_segment[block_index].beacon_block.clone(); assert!( - harness.chain.verify_block_for_gossip(block).await.is_ok(), + harness + .chain + .verify_block_for_gossip(block, cgc) + .await + .is_ok(), "the valid block should be processed" ); @@ -1239,7 +1247,7 @@ async fn block_gossip_verification() { matches!( harness .chain - .verify_block_for_gossip(block.clone()) + .verify_block_for_gossip(block.clone(), cgc) .await .expect_err("should error when processing known block"), BlockError::DuplicateImportStatusUnknown(_) @@ -1295,15 +1303,11 @@ async fn verify_and_process_gossip_data_sidecars( #[tokio::test] async fn verify_block_for_gossip_slashing_detection() { + create_test_tracing_subscriber(); let slasher_dir = tempdir().unwrap(); let spec = Arc::new(test_spec::()); let slasher = Arc::new( - Slasher::open( - SlasherConfig::new(slasher_dir.path().into()), - spec.clone(), - test_logger(), - ) - .unwrap(), + Slasher::open(SlasherConfig::new(slasher_dir.path().into()), spec.clone()).unwrap(), ); let inner_slasher = slasher.clone(); @@ -1319,8 +1323,17 @@ async fn verify_block_for_gossip_slashing_detection() { let state = harness.get_current_state(); let ((block1, blobs1), _) = harness.make_block(state.clone(), Slot::new(1)).await; let ((block2, _blobs2), _) = harness.make_block(state, Slot::new(1)).await; + let cgc = if block1.fork_name_unchecked().fulu_enabled() { + harness.get_sampling_column_count() + } else { + 0 + }; - let verified_block = harness.chain.verify_block_for_gossip(block1).await.unwrap(); + let verified_block = harness + .chain + .verify_block_for_gossip(block1, cgc) + .await + .unwrap(); if let Some((kzg_proofs, blobs)) = blobs1 { harness @@ -1343,7 +1356,7 @@ async fn verify_block_for_gossip_slashing_detection() { ) .await .unwrap(); - unwrap_err(harness.chain.verify_block_for_gossip(block2).await); + unwrap_err(harness.chain.verify_block_for_gossip(block2, CGC).await); // Slasher should have been handed the two conflicting blocks and crafted a slashing. slasher.process_queued(Epoch::new(0)).unwrap(); @@ -1367,7 +1380,11 @@ async fn verify_block_for_gossip_doppelganger_detection() { .attestations() .map(|att| att.clone_as_attestation()) .collect::>(); - let verified_block = harness.chain.verify_block_for_gossip(block).await.unwrap(); + let verified_block = harness + .chain + .verify_block_for_gossip(block, CGC) + .await + .unwrap(); harness .chain .process_block( @@ -1514,7 +1531,7 @@ async fn add_base_block_to_altair_chain() { assert!(matches!( harness .chain - .verify_block_for_gossip(Arc::new(base_block.clone())) + .verify_block_for_gossip(Arc::new(base_block.clone()), CGC) .await .expect_err("should error when processing base block"), BlockError::InconsistentFork(InconsistentFork { @@ -1650,7 +1667,7 @@ async fn add_altair_block_to_base_chain() { assert!(matches!( harness .chain - .verify_block_for_gossip(Arc::new(altair_block.clone())) + .verify_block_for_gossip(Arc::new(altair_block.clone()), CGC) .await .expect_err("should error when processing altair block"), BlockError::InconsistentFork(InconsistentFork { @@ -1814,3 +1831,14 @@ async fn import_execution_pending_block( } } } + +fn get_cgc(blobs_opt: &Option>) -> usize { + if let Some(data_sidecars) = blobs_opt.as_ref() { + match data_sidecars { + DataSidecars::Blobs(_) => 0, + DataSidecars::DataColumns(d) => d.len(), + } + } else { + 0 + } +} diff --git a/beacon_node/beacon_chain/tests/capella.rs b/beacon_node/beacon_chain/tests/capella.rs index 3ce5702f2e..2c2ba8e01a 100644 --- a/beacon_node/beacon_chain/tests/capella.rs +++ b/beacon_node/beacon_chain/tests/capella.rs @@ -40,7 +40,6 @@ async fn base_altair_bellatrix_capella() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/op_verification.rs b/beacon_node/beacon_chain/tests/op_verification.rs index 44fb298d6c..86ab0cce80 100644 --- a/beacon_node/beacon_chain/tests/op_verification.rs +++ b/beacon_node/beacon_chain/tests/op_verification.rs @@ -9,7 +9,6 @@ use beacon_chain::{ }, BeaconChainError, }; -use sloggers::{null::NullLoggerBuilder, Build}; use state_processing::per_block_processing::errors::{ AttesterSlashingInvalid, BlockOperationError, ExitInvalid, ProposerSlashingInvalid, }; @@ -35,7 +34,6 @@ fn get_store(db_path: &TempDir) -> Arc { let cold_path = db_path.path().join("cold_db"); let blobs_path = db_path.path().join("blobs_db"); let config = StoreConfig::default(); - let log = NullLoggerBuilder.build().expect("logger should build"); HotColdDB::open( &hot_path, &cold_path, @@ -43,7 +41,6 @@ fn get_store(db_path: &TempDir) -> Arc { |_, _, _| Ok(()), config, spec, - log, ) .expect("disk store should initialize") } diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 01b790bb25..88180f3c94 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -12,7 +12,6 @@ use execution_layer::{ ExecutionLayer, ForkchoiceState, PayloadAttributes, }; use fork_choice::{Error as ForkChoiceError, InvalidationOperation, PayloadVerificationStatus}; -use logging::test_logger; use proto_array::{Error as ProtoArrayError, ExecutionStatus}; use slot_clock::SlotClock; use std::collections::HashMap; @@ -22,6 +21,7 @@ use task_executor::ShutdownReason; use types::*; const VALIDATOR_COUNT: usize = 32; +const CGC: usize = 8; type E = MainnetEthSpec; @@ -56,7 +56,6 @@ impl InvalidPayloadRig { reconstruct_historic_states: true, ..ChainConfig::default() }) - .logger(test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .mock_execution_layer() .fresh_ephemeral_store() @@ -1052,7 +1051,7 @@ async fn invalid_parent() { // Ensure the block built atop an invalid payload is invalid for gossip. assert!(matches!( - rig.harness.chain.clone().verify_block_for_gossip(block.clone()).await, + rig.harness.chain.clone().verify_block_for_gossip(block.clone(), CGC).await, Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root }) if invalid_root == parent_root )); diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index 41e6467b0f..6226ed39cb 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -9,9 +9,7 @@ use beacon_chain::{ types::{Epoch, EthSpec, Keypair, MinimalEthSpec}, BlockError, ChainConfig, StateSkipConfig, WhenSlotSkipped, }; -use eth2::lighthouse::attestation_rewards::TotalAttestationRewards; -use eth2::lighthouse::StandardAttestationRewards; -use eth2::types::ValidatorId; +use eth2::types::{StandardAttestationRewards, TotalAttestationRewards, ValidatorId}; use state_processing::{BlockReplayError, BlockReplayer}; use std::array::IntoIter; use std::collections::HashMap; @@ -20,6 +18,9 @@ use types::{ChainSpec, ForkName, Slot}; pub const VALIDATOR_COUNT: usize = 64; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + type E = MinimalEthSpec; static KEYPAIRS: LazyLock> = @@ -116,8 +117,13 @@ async fn test_sync_committee_rewards() { .get_blinded_block(&block.parent_root()) .unwrap() .unwrap(); + let parent_state = chain - .get_state(&parent_block.state_root(), Some(parent_block.slot())) + .get_state( + &parent_block.state_root(), + Some(parent_block.slot()), + CACHE_STATE_IN_TESTS, + ) .unwrap() .unwrap(); @@ -328,8 +334,7 @@ async fn test_rewards_base_multi_inclusion() { .extend_slots(E::slots_per_epoch() as usize * 2 - 4) .await; - // pin to reduce stack size for clippy - Box::pin(check_all_base_rewards(&harness, initial_balances)).await; + check_all_base_rewards(&harness, initial_balances).await; } #[tokio::test] @@ -692,7 +697,8 @@ async fn check_all_base_rewards( harness: &BeaconChainHarness>, balances: Vec, ) { - check_all_base_rewards_for_subset(harness, balances, vec![]).await; + // The box reduces the size on the stack for a clippy lint. + Box::pin(check_all_base_rewards_for_subset(harness, balances, vec![])).await; } async fn check_all_base_rewards_for_subset( diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 7a2df76970..38ff87d0c8 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -14,7 +14,7 @@ use beacon_chain::{ migrate::MigratorConfig, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot, BlockError, ChainConfig, NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped, }; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use maplit::hashset; use rand::Rng; use slot_clock::{SlotClock, TestingSlotClock}; @@ -39,6 +39,9 @@ use types::*; pub const LOW_VALIDATOR_COUNT: usize = 24; pub const HIGH_VALIDATOR_COUNT: usize = 64; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + /// A cached set of keys. static KEYPAIRS: LazyLock> = LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(HIGH_VALIDATOR_COUNT)); @@ -59,10 +62,10 @@ fn get_store_generic( config: StoreConfig, spec: ChainSpec, ) -> Arc, BeaconNodeBackend>> { + create_test_tracing_subscriber(); let hot_path = db_path.path().join("chain_db"); let cold_path = db_path.path().join("freezer_db"); let blobs_path = db_path.path().join("blobs_db"); - let log = test_logger(); HotColdDB::open( &hot_path, @@ -71,7 +74,6 @@ fn get_store_generic( |_, _, _| Ok(()), config, spec.into(), - log, ) .expect("disk store should initialize") } @@ -109,7 +111,6 @@ fn get_harness_generic( let harness = TestHarness::builder(MinimalEthSpec) .spec(store.get_chain_spec().clone()) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(store.logger().clone()) .fresh_disk_store(store) .mock_execution_layer() .chain_config(chain_config) @@ -758,6 +759,7 @@ async fn delete_blocks_and_states() { .get_state( &faulty_head_block.state_root(), Some(faulty_head_block.slot()), + CACHE_STATE_IN_TESTS, ) .expect("no db error") .expect("faulty head state exists"); @@ -771,7 +773,12 @@ async fn delete_blocks_and_states() { break; } store.delete_state(&state_root, slot).unwrap(); - assert_eq!(store.get_state(&state_root, Some(slot)).unwrap(), None); + assert_eq!( + store + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) + .unwrap(), + None + ); } // Double-deleting should also be OK (deleting non-existent things is fine) @@ -1055,7 +1062,11 @@ fn get_state_for_block(harness: &TestHarness, block_root: Hash256) -> BeaconStat .unwrap(); harness .chain - .get_state(&head_block.state_root(), Some(head_block.slot())) + .get_state( + &head_block.state_root(), + Some(head_block.slot()), + CACHE_STATE_IN_TESTS, + ) .unwrap() .unwrap() } @@ -1892,7 +1903,10 @@ fn check_all_states_exist<'a>( states: impl Iterator, ) { for &state_hash in states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); + let state = harness + .chain + .get_state(&state_hash.into(), None, CACHE_STATE_IN_TESTS) + .unwrap(); assert!( state.is_some(), "expected state {:?} to be in DB", @@ -1910,7 +1924,7 @@ fn check_no_states_exist<'a>( assert!( harness .chain - .get_state(&state_root.into(), None) + .get_state(&state_root.into(), None, CACHE_STATE_IN_TESTS) .unwrap() .is_none(), "state {:?} should not be in the DB", @@ -2344,7 +2358,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .get_or_reconstruct_blobs(&wss_block_root) .unwrap(); let wss_state = full_store - .get_state(&wss_state_root, Some(checkpoint_slot)) + .get_state(&wss_state_root, Some(checkpoint_slot), CACHE_STATE_IN_TESTS) .unwrap() .unwrap(); @@ -2359,7 +2373,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .await; let (shutdown_tx, _shutdown_rx) = futures::channel::mpsc::channel(1); - let log = harness.chain.logger().clone(); + let temp2 = tempdir().unwrap(); let store = get_store(&temp2); let spec = test_spec::(); @@ -2385,7 +2399,6 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .store(store.clone()) .custom_spec(test_spec::().into()) .task_executor(harness.chain.task_executor.clone()) - .logger(log.clone()) .weak_subjectivity_state( wss_state, wss_block.clone(), @@ -2399,10 +2412,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .slot_clock(slot_clock) .shutdown_sender(shutdown_tx) .chain_config(ChainConfig::default()) - .event_handler(Some(ServerSentEventHandler::new_with_capacity( - log.clone(), - 1, - ))) + .event_handler(Some(ServerSentEventHandler::new_with_capacity(1))) .execution_layer(Some(mock.el)) .build() .expect("should build"); @@ -2460,7 +2470,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { // Check that the new block's state can be loaded correctly. let mut state = beacon_chain .store - .get_state(&state_root, Some(slot)) + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) .unwrap() .unwrap(); assert_eq!(state.update_tree_hash_cache().unwrap(), state_root); @@ -2517,18 +2527,13 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { // Corrupt the signature on the 1st block to ensure that the backfill processor is checking // signatures correctly. Regression test for https://github.com/sigp/lighthouse/pull/5120. - let mut batch_with_invalid_first_block = available_blocks.clone(); + let mut batch_with_invalid_first_block = + available_blocks.iter().map(clone_block).collect::>(); batch_with_invalid_first_block[0] = { - let (block_root, block, blobs, data_columns) = available_blocks[0].clone().deconstruct(); + let (block_root, block, data) = clone_block(&available_blocks[0]).deconstruct(); let mut corrupt_block = (*block).clone(); *corrupt_block.signature_mut() = Signature::empty(); - AvailableBlock::__new_for_testing( - block_root, - Arc::new(corrupt_block), - blobs, - data_columns, - Arc::new(spec), - ) + AvailableBlock::__new_for_testing(block_root, Arc::new(corrupt_block), data, Arc::new(spec)) }; // Importing the invalid batch should error. @@ -2540,8 +2545,9 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { )); // Importing the batch with valid signatures should succeed. + let available_blocks_dup = available_blocks.iter().map(clone_block).collect::>(); beacon_chain - .import_historical_block_batch(available_blocks.clone()) + .import_historical_block_batch(available_blocks_dup) .unwrap(); assert_eq!(beacon_chain.store.get_oldest_block_slot(), 0); @@ -2594,7 +2600,10 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .unwrap() .map(Result::unwrap) { - let mut state = store.get_state(&state_root, Some(slot)).unwrap().unwrap(); + let mut state = store + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) + .unwrap() + .unwrap(); assert_eq!(state.slot(), slot); assert_eq!(state.canonical_root().unwrap(), state_root); } @@ -3058,7 +3067,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, CURRENT_SCHEMA_VERSION, min_version, - store.logger().clone(), ) .expect("schema downgrade to minimum version should work"); @@ -3068,7 +3076,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, min_version, CURRENT_SCHEMA_VERSION, - store.logger().clone(), ) .expect("schema upgrade from minimum version should work"); @@ -3076,7 +3083,6 @@ async fn schema_downgrade_to_min_version() { let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() .keypairs(KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec()) - .logger(store.logger().clone()) .testing_slot_clock(slot_clock) .resumed_disk_store(store.clone()) .mock_execution_layer() @@ -3094,7 +3100,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, CURRENT_SCHEMA_VERSION, min_version_sub_1, - harness.logger().clone(), ) .expect_err("should not downgrade below minimum version"); } @@ -3424,9 +3429,10 @@ async fn prune_historic_states() { let store = get_store(&db_path); let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); let genesis_state_root = harness.chain.genesis_state_root; + let genesis_state = harness .chain - .get_state(&genesis_state_root, None) + .get_state(&genesis_state_root, None, CACHE_STATE_IN_TESTS) .unwrap() .unwrap(); @@ -3447,7 +3453,10 @@ async fn prune_historic_states() { .map(Result::unwrap) .collect::>(); for &(state_root, slot) in &first_epoch_state_roots { - assert!(store.get_state(&state_root, Some(slot)).unwrap().is_some()); + assert!(store + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) + .unwrap() + .is_some()); } store @@ -3462,7 +3471,10 @@ async fn prune_historic_states() { // Ensure all epoch 0 states other than the genesis have been pruned. for &(state_root, slot) in &first_epoch_state_roots { assert_eq!( - store.get_state(&state_root, Some(slot)).unwrap().is_some(), + store + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) + .unwrap() + .is_some(), slot == 0 ); } @@ -3588,7 +3600,7 @@ fn check_chain_dump(harness: &TestHarness, expected_len: u64) { harness .chain .store - .get_state(&checkpoint.beacon_state_root(), None) + .get_state(&checkpoint.beacon_state_root(), None, CACHE_STATE_IN_TESTS) .expect("no error") .expect("state exists") .slot(), @@ -3650,7 +3662,7 @@ fn check_iterators(harness: &TestHarness) { harness .chain .store - .get_state(&state_root, Some(slot)) + .get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS) .unwrap() .is_some(), "state {:?} from canonical chain should be in DB", @@ -3690,3 +3702,7 @@ fn get_blocks( .map(|checkpoint| checkpoint.beacon_block_root.into()) .collect() } + +fn clone_block(block: &AvailableBlock) -> AvailableBlock { + block.__clone_without_recv().unwrap() +} diff --git a/beacon_node/beacon_chain/tests/sync_committee_verification.rs b/beacon_node/beacon_chain/tests/sync_committee_verification.rs index 6d30b8a4e3..c8bbcce20d 100644 --- a/beacon_node/beacon_chain/tests/sync_committee_verification.rs +++ b/beacon_node/beacon_chain/tests/sync_committee_verification.rs @@ -21,6 +21,9 @@ pub type E = MainnetEthSpec; pub const VALIDATOR_COUNT: usize = 256; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + /// A cached set of keys. static KEYPAIRS: LazyLock> = LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT)); @@ -755,7 +758,10 @@ async fn unaggregated_gossip_verification() { // Load the block and state for the given root. let block = chain.get_block(&root).await.unwrap().unwrap(); - let mut state = chain.get_state(&block.state_root(), None).unwrap().unwrap(); + let mut state = chain + .get_state(&block.state_root(), None, CACHE_STATE_IN_TESTS) + .unwrap() + .unwrap(); // Advance the state to simulate a pre-state for block production. let slot = valid_sync_committee_message.slot + 1; diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index c641f32b82..c801361fd5 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -12,10 +12,12 @@ use operation_pool::PersistedOperationPool; use state_processing::{per_slot_processing, per_slot_processing::Error as SlotProcessingError}; use std::sync::LazyLock; use types::{ - BeaconState, BeaconStateError, BlockImportSource, EthSpec, Hash256, Keypair, MinimalEthSpec, - RelativeEpoch, Slot, + BeaconState, BeaconStateError, BlockImportSource, Checkpoint, EthSpec, Hash256, Keypair, + MinimalEthSpec, RelativeEpoch, Slot, }; +type E = MinimalEthSpec; + // Should ideally be divisible by 3. pub const VALIDATOR_COUNT: usize = 48; @@ -24,12 +26,22 @@ static KEYPAIRS: LazyLock> = LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT)); fn get_harness(validator_count: usize) -> BeaconChainHarness> { + get_harness_with_config( + validator_count, + ChainConfig { + reconstruct_historic_states: true, + ..Default::default() + }, + ) +} + +fn get_harness_with_config( + validator_count: usize, + chain_config: ChainConfig, +) -> BeaconChainHarness> { let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() - .chain_config(ChainConfig { - reconstruct_historic_states: true, - ..ChainConfig::default() - }) + .chain_config(chain_config) .keypairs(KEYPAIRS[0..validator_count].to_vec()) .fresh_ephemeral_store() .mock_execution_layer() @@ -869,3 +881,165 @@ async fn block_roots_skip_slot_behaviour() { "WhenSlotSkipped::Prev should return None on a future slot" ); } + +async fn pseudo_finalize_test_generic( + epochs_per_migration: u64, + expect_true_finalization_migration: bool, +) { + // This test ensures that after pseudo finalization, we can still finalize the chain without issues + let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; + + let chain_config = ChainConfig { + reconstruct_historic_states: true, + epochs_per_migration, + ..Default::default() + }; + let harness = get_harness_with_config(VALIDATOR_COUNT, chain_config); + + let one_third = VALIDATOR_COUNT / 3; + let attesters = (0..one_third).collect(); + + // extend the chain, but don't finalize + harness + .extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(attesters), + ) + .await; + + harness.advance_slot(); + + let head = harness.chain.head_snapshot(); + let state = &head.beacon_state; + let split = harness.chain.store.get_split_info(); + + assert_eq!( + state.slot(), + num_blocks_produced, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + num_blocks_produced / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + assert_eq!( + state.current_justified_checkpoint().epoch, + 0, + "There should be no justified checkpoint" + ); + assert_eq!( + state.finalized_checkpoint().epoch, + 0, + "There should be no finalized checkpoint" + ); + assert_eq!(split.slot, 0, "Our split point should be unset"); + + let checkpoint = Checkpoint { + epoch: head.beacon_state.current_epoch(), + root: head.beacon_block_root, + }; + + // pseudo finalize + harness + .chain + .manually_finalize_state(head.beacon_state_root(), checkpoint) + .unwrap(); + + let split = harness.chain.store.get_split_info(); + let pseudo_finalized_slot = split.slot; + + assert_eq!( + state.current_justified_checkpoint().epoch, + 0, + "We pseudo finalized, but our justified checkpoint should still be unset" + ); + assert_eq!( + state.finalized_checkpoint().epoch, + 0, + "We pseudo finalized, but our finalized checkpoint should still be unset" + ); + assert_eq!( + split.slot, + head.beacon_state.slot(), + "We pseudo finalized, our split point should be at the current head slot" + ); + + // finalize the chain + harness + .extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + harness.advance_slot(); + + let head = harness.chain.head_snapshot(); + let state = &head.beacon_state; + let split = harness.chain.store.get_split_info(); + + assert_eq!( + state.slot(), + num_blocks_produced * 2, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + (num_blocks_produced * 2) / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + assert_eq!( + state.current_justified_checkpoint().epoch, + state.current_epoch() - 1, + "the head should be justified one behind the current epoch" + ); + let finalized_epoch = state.finalized_checkpoint().epoch; + assert_eq!( + finalized_epoch, + state.current_epoch() - 2, + "the head should be finalized two behind the current epoch" + ); + + let expected_split_slot = if pseudo_finalized_slot.epoch(E::slots_per_epoch()) + + epochs_per_migration + > finalized_epoch + { + pseudo_finalized_slot + } else { + finalized_epoch.start_slot(E::slots_per_epoch()) + }; + assert_eq!( + split.slot, expected_split_slot, + "We finalized, our split point should be updated according to epochs_per_migration" + ); + + // In the case that we did not process the true finalization migration (due to + // epochs_per_migration), check that the chain finalized *despite* the absence of the split + // block in fork choice. + // This is a regression test for https://github.com/sigp/lighthouse/pull/7105 + if !expect_true_finalization_migration { + assert_eq!(expected_split_slot, pseudo_finalized_slot); + assert!(!harness + .chain + .canonical_head + .fork_choice_read_lock() + .contains_block(&split.block_root)); + } +} + +#[tokio::test] +async fn pseudo_finalize_basic() { + let epochs_per_migration = 0; + let expect_true_migration = true; + pseudo_finalize_test_generic(epochs_per_migration, expect_true_migration).await; +} + +#[tokio::test] +async fn pseudo_finalize_with_lagging_split_update() { + let epochs_per_migration = 10; + let expect_true_migration = false; + pseudo_finalize_test_generic(epochs_per_migration, expect_true_migration).await; +} diff --git a/beacon_node/beacon_chain/tests/validator_monitor.rs b/beacon_node/beacon_chain/tests/validator_monitor.rs index 180db6d76d..bca37b4e6d 100644 --- a/beacon_node/beacon_chain/tests/validator_monitor.rs +++ b/beacon_node/beacon_chain/tests/validator_monitor.rs @@ -2,7 +2,6 @@ use beacon_chain::test_utils::{ AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, }; use beacon_chain::validator_monitor::{ValidatorMonitorConfig, MISSED_BLOCK_LAG_SLOTS}; -use logging::test_logger; use std::sync::LazyLock; use types::{Epoch, EthSpec, Keypair, MainnetEthSpec, PublicKeyBytes, Slot}; @@ -22,7 +21,6 @@ fn get_harness( let harness = BeaconChainHarness::builder(MainnetEthSpec) .default_spec() .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .validator_monitor_config(ValidatorMonitorConfig { diff --git a/beacon_node/beacon_processor/Cargo.toml b/beacon_node/beacon_processor/Cargo.toml index c96e0868d7..afd4660c9a 100644 --- a/beacon_node/beacon_processor/Cargo.toml +++ b/beacon_node/beacon_processor/Cargo.toml @@ -13,12 +13,12 @@ metrics = { workspace = true } num_cpus = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } strum = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/beacon_node/beacon_processor/src/lib.rs b/beacon_node/beacon_processor/src/lib.rs index 2743f93bb3..e864cb1fd9 100644 --- a/beacon_node/beacon_processor/src/lib.rs +++ b/beacon_node/beacon_processor/src/lib.rs @@ -44,10 +44,10 @@ use crate::work_reprocessing_queue::{ use futures::stream::{Stream, StreamExt}; use futures::task::Poll; use lighthouse_network::{MessageId, NetworkGlobals, PeerId}; +use logging::crit; use logging::TimeLatch; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, trace, warn, Logger}; use slot_clock::SlotClock; use std::cmp; use std::collections::{HashSet, VecDeque}; @@ -61,6 +61,7 @@ use strum::IntoStaticStr; use task_executor::TaskExecutor; use tokio::sync::mpsc; use tokio::sync::mpsc::error::TrySendError; +use tracing::{debug, error, trace, warn}; use types::{ Attestation, BeaconState, ChainSpec, EthSpec, Hash256, RelativeEpoch, SignedAggregateAndProof, SingleAttestation, Slot, SubnetId, @@ -109,8 +110,6 @@ pub struct BeaconProcessorQueueLengths { gossip_voluntary_exit_queue: usize, gossip_proposer_slashing_queue: usize, gossip_attester_slashing_queue: usize, - finality_update_queue: usize, - optimistic_update_queue: usize, unknown_light_client_update_queue: usize, unknown_block_sampling_request_queue: usize, rpc_block_queue: usize, @@ -132,9 +131,11 @@ pub struct BeaconProcessorQueueLengths { dcbroots_queue: usize, dcbrange_queue: usize, gossip_bls_to_execution_change_queue: usize, + lc_gossip_finality_update_queue: usize, + lc_gossip_optimistic_update_queue: usize, lc_bootstrap_queue: usize, - lc_optimistic_update_queue: usize, - lc_finality_update_queue: usize, + lc_rpc_optimistic_update_queue: usize, + lc_rpc_finality_update_queue: usize, lc_update_range_queue: usize, api_request_p0_queue: usize, api_request_p1_queue: usize, @@ -175,15 +176,13 @@ impl BeaconProcessorQueueLengths { gossip_voluntary_exit_queue: 4096, gossip_proposer_slashing_queue: 4096, gossip_attester_slashing_queue: 4096, - finality_update_queue: 1024, - optimistic_update_queue: 1024, - unknown_block_sampling_request_queue: 16384, unknown_light_client_update_queue: 128, rpc_block_queue: 1024, rpc_blob_queue: 1024, // TODO(das): Placeholder values rpc_custody_column_queue: 1000, rpc_verify_data_column_queue: 1000, + unknown_block_sampling_request_queue: 16384, sampling_result_queue: 1000, chain_segment_queue: 64, backfill_chain_segment: 64, @@ -200,9 +199,11 @@ impl BeaconProcessorQueueLengths { dcbroots_queue: 1024, dcbrange_queue: 1024, gossip_bls_to_execution_change_queue: 16384, + lc_gossip_finality_update_queue: 1024, + lc_gossip_optimistic_update_queue: 1024, lc_bootstrap_queue: 1024, - lc_optimistic_update_queue: 512, - lc_finality_update_queue: 512, + lc_rpc_optimistic_update_queue: 512, + lc_rpc_finality_update_queue: 512, lc_update_range_queue: 512, api_request_p0_queue: 1024, api_request_p1_queue: 1024, @@ -305,14 +306,13 @@ impl FifoQueue { /// Add a new item to the queue. /// /// Drops `item` if the queue is full. - pub fn push(&mut self, item: T, item_desc: &str, log: &Logger) { + pub fn push(&mut self, item: T, item_desc: &str) { if self.queue.len() == self.max_length { error!( - log, - "Work queue is full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => self.max_length, - "queue" => item_desc, + msg = "the system has insufficient resources for load", + queue_len = self.max_length, + queue = item_desc, + "Work queue is full" ) } else { self.queue.push_back(item); @@ -827,7 +827,6 @@ pub struct BeaconProcessor { pub executor: TaskExecutor, pub current_workers: usize, pub config: BeaconProcessorConfig, - pub log: Logger, } impl BeaconProcessor { @@ -884,21 +883,16 @@ impl BeaconProcessor { let mut gossip_attester_slashing_queue = FifoQueue::new(queue_lengths.gossip_attester_slashing_queue); - // Using a FIFO queue for light client updates to maintain sequence order. - let mut finality_update_queue = FifoQueue::new(queue_lengths.finality_update_queue); - let mut optimistic_update_queue = FifoQueue::new(queue_lengths.optimistic_update_queue); - let mut unknown_light_client_update_queue = - FifoQueue::new(queue_lengths.unknown_light_client_update_queue); - let mut unknown_block_sampling_request_queue = - FifoQueue::new(queue_lengths.unknown_block_sampling_request_queue); - // Using a FIFO queue since blocks need to be imported sequentially. let mut rpc_block_queue = FifoQueue::new(queue_lengths.rpc_block_queue); let mut rpc_blob_queue = FifoQueue::new(queue_lengths.rpc_blob_queue); let mut rpc_custody_column_queue = FifoQueue::new(queue_lengths.rpc_custody_column_queue); let mut rpc_verify_data_column_queue = FifoQueue::new(queue_lengths.rpc_verify_data_column_queue); + // TODO(das): the sampling_request_queue is never read let mut sampling_result_queue = FifoQueue::new(queue_lengths.sampling_result_queue); + let mut unknown_block_sampling_request_queue = + FifoQueue::new(queue_lengths.unknown_block_sampling_request_queue); let mut chain_segment_queue = FifoQueue::new(queue_lengths.chain_segment_queue); let mut backfill_chain_segment = FifoQueue::new(queue_lengths.backfill_chain_segment); let mut gossip_block_queue = FifoQueue::new(queue_lengths.gossip_block_queue); @@ -917,10 +911,18 @@ impl BeaconProcessor { let mut gossip_bls_to_execution_change_queue = FifoQueue::new(queue_lengths.gossip_bls_to_execution_change_queue); + // Using FIFO queues for light client updates to maintain sequence order. + let mut lc_gossip_finality_update_queue = + FifoQueue::new(queue_lengths.lc_gossip_finality_update_queue); + let mut lc_gossip_optimistic_update_queue = + FifoQueue::new(queue_lengths.lc_gossip_optimistic_update_queue); + let mut unknown_light_client_update_queue = + FifoQueue::new(queue_lengths.unknown_light_client_update_queue); let mut lc_bootstrap_queue = FifoQueue::new(queue_lengths.lc_bootstrap_queue); - let mut lc_optimistic_update_queue = - FifoQueue::new(queue_lengths.lc_optimistic_update_queue); - let mut lc_finality_update_queue = FifoQueue::new(queue_lengths.lc_finality_update_queue); + let mut lc_rpc_optimistic_update_queue = + FifoQueue::new(queue_lengths.lc_rpc_optimistic_update_queue); + let mut lc_rpc_finality_update_queue = + FifoQueue::new(queue_lengths.lc_rpc_finality_update_queue); let mut lc_update_range_queue = FifoQueue::new(queue_lengths.lc_update_range_queue); let mut api_request_p0_queue = FifoQueue::new(queue_lengths.api_request_p0_queue); @@ -935,7 +937,6 @@ impl BeaconProcessor { work_reprocessing_rx, &self.executor, Arc::new(slot_clock), - self.log.clone(), maximum_gossip_clock_disparity, )?; @@ -966,9 +967,8 @@ impl BeaconProcessor { { Err(e) => { warn!( - self.log, - "Unable to queue backfill work event. Will try to process now."; - "error" => %e + error = %e, + "Unable to queue backfill work event. Will try to process now." ); match e { TrySendError::Full(reprocess_queue_message) @@ -979,9 +979,8 @@ impl BeaconProcessor { ) => Some(backfill_batch.into()), other => { crit!( - self.log, - "Unexpected queue message type"; - "message_type" => other.as_ref() + message_type = other.as_ref(), + "Unexpected queue message type" ); // This is an unhandled exception, drop the message. continue; @@ -1002,11 +1001,7 @@ impl BeaconProcessor { Some(InboundEvent::WorkEvent(event)) | Some(InboundEvent::ReprocessingWork(event)) => Some(event), None => { - debug!( - self.log, - "Gossip processor stopped"; - "msg" => "stream ended" - ); + debug!(msg = "stream ended", "Gossip processor stopped"); break; } }; @@ -1047,230 +1042,234 @@ impl BeaconProcessor { None if can_spawn => { // Check for chain segments first, they're the most efficient way to get // blocks into the system. - let work_event: Option> = if let Some(item) = - chain_segment_queue.pop() - { - Some(item) - // Check sync blocks before gossip blocks, since we've already explicitly - // requested these blocks. - } else if let Some(item) = rpc_block_queue.pop() { - Some(item) - } else if let Some(item) = rpc_blob_queue.pop() { - Some(item) - } else if let Some(item) = rpc_custody_column_queue.pop() { - Some(item) - // TODO(das): decide proper prioritization for sampling columns - } else if let Some(item) = rpc_custody_column_queue.pop() { - Some(item) - } else if let Some(item) = rpc_verify_data_column_queue.pop() { - Some(item) - } else if let Some(item) = sampling_result_queue.pop() { - Some(item) - // Check delayed blocks before gossip blocks, the gossip blocks might rely - // on the delayed ones. - } else if let Some(item) = delayed_block_queue.pop() { - Some(item) - // Check gossip blocks before gossip attestations, since a block might be - // required to verify some attestations. - } else if let Some(item) = gossip_block_queue.pop() { - Some(item) - } else if let Some(item) = gossip_blob_queue.pop() { - Some(item) - } else if let Some(item) = gossip_data_column_queue.pop() { - Some(item) - // Check the priority 0 API requests after blocks and blobs, but before attestations. - } else if let Some(item) = api_request_p0_queue.pop() { - Some(item) - // Check the aggregates, *then* the unaggregates since we assume that - // aggregates are more valuable to local validators and effectively give us - // more information with less signature verification time. - } else if aggregate_queue.len() > 0 { - let batch_size = cmp::min( - aggregate_queue.len(), - self.config.max_gossip_aggregate_batch_size, - ); + let work_event: Option> = + if let Some(item) = chain_segment_queue.pop() { + Some(item) + // Check sync blocks before gossip blocks, since we've already explicitly + // requested these blocks. + } else if let Some(item) = rpc_block_queue.pop() { + Some(item) + } else if let Some(item) = rpc_blob_queue.pop() { + Some(item) + } else if let Some(item) = rpc_custody_column_queue.pop() { + Some(item) + // TODO(das): decide proper prioritization for sampling columns + } else if let Some(item) = rpc_custody_column_queue.pop() { + Some(item) + } else if let Some(item) = rpc_verify_data_column_queue.pop() { + Some(item) + } else if let Some(item) = sampling_result_queue.pop() { + Some(item) + // Check delayed blocks before gossip blocks, the gossip blocks might rely + // on the delayed ones. + } else if let Some(item) = delayed_block_queue.pop() { + Some(item) + // Check gossip blocks before gossip attestations, since a block might be + // required to verify some attestations. + } else if let Some(item) = gossip_block_queue.pop() { + Some(item) + } else if let Some(item) = gossip_blob_queue.pop() { + Some(item) + } else if let Some(item) = gossip_data_column_queue.pop() { + Some(item) + // Check the priority 0 API requests after blocks and blobs, but before attestations. + } else if let Some(item) = api_request_p0_queue.pop() { + Some(item) + // Check the aggregates, *then* the unaggregates since we assume that + // aggregates are more valuable to local validators and effectively give us + // more information with less signature verification time. + } else if aggregate_queue.len() > 0 { + let batch_size = cmp::min( + aggregate_queue.len(), + self.config.max_gossip_aggregate_batch_size, + ); - if batch_size < 2 { - // One single aggregate is in the queue, process it individually. - aggregate_queue.pop() - } else { - // Collect two or more aggregates into a batch, so they can take - // advantage of batch signature verification. - // - // Note: this will convert the `Work::GossipAggregate` item into a - // `Work::GossipAggregateBatch` item. - let mut aggregates = Vec::with_capacity(batch_size); - let mut process_batch_opt = None; - for _ in 0..batch_size { - if let Some(item) = aggregate_queue.pop() { - match item { - Work::GossipAggregate { - aggregate, - process_individual: _, - process_batch, - } => { - aggregates.push(*aggregate); - if process_batch_opt.is_none() { - process_batch_opt = Some(process_batch); + if batch_size < 2 { + // One single aggregate is in the queue, process it individually. + aggregate_queue.pop() + } else { + // Collect two or more aggregates into a batch, so they can take + // advantage of batch signature verification. + // + // Note: this will convert the `Work::GossipAggregate` item into a + // `Work::GossipAggregateBatch` item. + let mut aggregates = Vec::with_capacity(batch_size); + let mut process_batch_opt = None; + for _ in 0..batch_size { + if let Some(item) = aggregate_queue.pop() { + match item { + Work::GossipAggregate { + aggregate, + process_individual: _, + process_batch, + } => { + aggregates.push(*aggregate); + if process_batch_opt.is_none() { + process_batch_opt = Some(process_batch); + } + } + _ => { + error!("Invalid item in aggregate queue"); } - } - _ => { - error!(self.log, "Invalid item in aggregate queue"); } } } - } - if let Some(process_batch) = process_batch_opt { - // Process all aggregates with a single worker. - Some(Work::GossipAggregateBatch { - aggregates, - process_batch, - }) - } else { - // There is no good reason for this to - // happen, it is a serious logic error. - // Since we only form batches when multiple - // work items exist, we should always have a - // work closure at this point. - crit!(self.log, "Missing aggregate work"); - None - } - } - // Check the unaggregated attestation queue. - // - // Potentially use batching. - } else if attestation_queue.len() > 0 { - let batch_size = cmp::min( - attestation_queue.len(), - self.config.max_gossip_attestation_batch_size, - ); - - if batch_size < 2 { - // One single attestation is in the queue, process it individually. - attestation_queue.pop() - } else { - // Collect two or more attestations into a batch, so they can take - // advantage of batch signature verification. - // - // Note: this will convert the `Work::GossipAttestation` item into a - // `Work::GossipAttestationBatch` item. - let mut attestations = Vec::with_capacity(batch_size); - let mut process_batch_opt = None; - for _ in 0..batch_size { - if let Some(item) = attestation_queue.pop() { - match item { - Work::GossipAttestation { - attestation, - process_individual: _, - process_batch, - } => { - attestations.push(*attestation); - if process_batch_opt.is_none() { - process_batch_opt = Some(process_batch); - } - } - _ => error!( - self.log, - "Invalid item in attestation queue" - ), - } + if let Some(process_batch) = process_batch_opt { + // Process all aggregates with a single worker. + Some(Work::GossipAggregateBatch { + aggregates, + process_batch, + }) + } else { + // There is no good reason for this to + // happen, it is a serious logic error. + // Since we only form batches when multiple + // work items exist, we should always have a + // work closure at this point. + crit!("Missing aggregate work"); + None } } + // Check the unaggregated attestation queue. + // + // Potentially use batching. + } else if attestation_queue.len() > 0 { + let batch_size = cmp::min( + attestation_queue.len(), + self.config.max_gossip_attestation_batch_size, + ); - if let Some(process_batch) = process_batch_opt { - // Process all attestations with a single worker. - Some(Work::GossipAttestationBatch { - attestations, - process_batch, - }) + if batch_size < 2 { + // One single attestation is in the queue, process it individually. + attestation_queue.pop() } else { - // There is no good reason for this to - // happen, it is a serious logic error. - // Since we only form batches when multiple - // work items exist, we should always have a - // work closure at this point. - crit!(self.log, "Missing attestations work"); - None + // Collect two or more attestations into a batch, so they can take + // advantage of batch signature verification. + // + // Note: this will convert the `Work::GossipAttestation` item into a + // `Work::GossipAttestationBatch` item. + let mut attestations = Vec::with_capacity(batch_size); + let mut process_batch_opt = None; + for _ in 0..batch_size { + if let Some(item) = attestation_queue.pop() { + match item { + Work::GossipAttestation { + attestation, + process_individual: _, + process_batch, + } => { + attestations.push(*attestation); + if process_batch_opt.is_none() { + process_batch_opt = Some(process_batch); + } + } + _ => error!("Invalid item in attestation queue"), + } + } + } + + if let Some(process_batch) = process_batch_opt { + // Process all attestations with a single worker. + Some(Work::GossipAttestationBatch { + attestations, + process_batch, + }) + } else { + // There is no good reason for this to + // happen, it is a serious logic error. + // Since we only form batches when multiple + // work items exist, we should always have a + // work closure at this point. + crit!("Missing attestations work"); + None + } } - } - // Convert any gossip attestations that need to be converted. - } else if let Some(item) = attestation_to_convert_queue.pop() { - Some(item) - // Check sync committee messages after attestations as their rewards are lesser - // and they don't influence fork choice. - } else if let Some(item) = sync_contribution_queue.pop() { - Some(item) - } else if let Some(item) = sync_message_queue.pop() { - Some(item) - // Aggregates and unaggregates queued for re-processing are older and we - // care about fresher ones, so check those first. - } else if let Some(item) = unknown_block_aggregate_queue.pop() { - Some(item) - } else if let Some(item) = unknown_block_attestation_queue.pop() { - Some(item) - // Check RPC methods next. Status messages are needed for sync so - // prioritize them over syncing requests from other peers (BlocksByRange - // and BlocksByRoot) - } else if let Some(item) = status_queue.pop() { - Some(item) - } else if let Some(item) = bbrange_queue.pop() { - Some(item) - } else if let Some(item) = bbroots_queue.pop() { - Some(item) - } else if let Some(item) = blbrange_queue.pop() { - Some(item) - } else if let Some(item) = blbroots_queue.pop() { - Some(item) - } else if let Some(item) = dcbroots_queue.pop() { - Some(item) - } else if let Some(item) = dcbrange_queue.pop() { - Some(item) - // Prioritize sampling requests after block syncing requests - } else if let Some(item) = unknown_block_sampling_request_queue.pop() { - Some(item) - // Check slashings after all other consensus messages so we prioritize - // following head. - // - // Check attester slashings before proposer slashings since they have the - // potential to slash multiple validators at once. - } else if let Some(item) = gossip_attester_slashing_queue.pop() { - Some(item) - } else if let Some(item) = gossip_proposer_slashing_queue.pop() { - Some(item) - // Check exits and address changes late since our validators don't get - // rewards from them. - } else if let Some(item) = gossip_voluntary_exit_queue.pop() { - Some(item) - } else if let Some(item) = gossip_bls_to_execution_change_queue.pop() { - Some(item) - // Check the priority 1 API requests after we've - // processed all the interesting things from the network - // and things required for us to stay in good repute - // with our P2P peers. - } else if let Some(item) = api_request_p1_queue.pop() { - Some(item) - // Handle backfill sync chain segments. - } else if let Some(item) = backfill_chain_segment.pop() { - Some(item) - // Handle light client requests. - } else if let Some(item) = lc_bootstrap_queue.pop() { - Some(item) - } else if let Some(item) = lc_optimistic_update_queue.pop() { - Some(item) - } else if let Some(item) = lc_finality_update_queue.pop() { - Some(item) - // This statement should always be the final else statement. - } else { - // Let the journal know that a worker is freed and there's nothing else - // for it to do. - if let Some(work_journal_tx) = &work_journal_tx { - // We don't care if this message was successfully sent, we only use the journal - // during testing. - let _ = work_journal_tx.try_send(NOTHING_TO_DO); - } - None - }; + // Convert any gossip attestations that need to be converted. + } else if let Some(item) = attestation_to_convert_queue.pop() { + Some(item) + // Check sync committee messages after attestations as their rewards are lesser + // and they don't influence fork choice. + } else if let Some(item) = sync_contribution_queue.pop() { + Some(item) + } else if let Some(item) = sync_message_queue.pop() { + Some(item) + // Aggregates and unaggregates queued for re-processing are older and we + // care about fresher ones, so check those first. + } else if let Some(item) = unknown_block_aggregate_queue.pop() { + Some(item) + } else if let Some(item) = unknown_block_attestation_queue.pop() { + Some(item) + // Check RPC methods next. Status messages are needed for sync so + // prioritize them over syncing requests from other peers (BlocksByRange + // and BlocksByRoot) + } else if let Some(item) = status_queue.pop() { + Some(item) + } else if let Some(item) = bbrange_queue.pop() { + Some(item) + } else if let Some(item) = bbroots_queue.pop() { + Some(item) + } else if let Some(item) = blbrange_queue.pop() { + Some(item) + } else if let Some(item) = blbroots_queue.pop() { + Some(item) + } else if let Some(item) = dcbroots_queue.pop() { + Some(item) + } else if let Some(item) = dcbrange_queue.pop() { + Some(item) + // Prioritize sampling requests after block syncing requests + } else if let Some(item) = unknown_block_sampling_request_queue.pop() { + Some(item) + // Check slashings after all other consensus messages so we prioritize + // following head. + // + // Check attester slashings before proposer slashings since they have the + // potential to slash multiple validators at once. + } else if let Some(item) = gossip_attester_slashing_queue.pop() { + Some(item) + } else if let Some(item) = gossip_proposer_slashing_queue.pop() { + Some(item) + // Check exits and address changes late since our validators don't get + // rewards from them. + } else if let Some(item) = gossip_voluntary_exit_queue.pop() { + Some(item) + } else if let Some(item) = gossip_bls_to_execution_change_queue.pop() { + Some(item) + // Check the priority 1 API requests after we've + // processed all the interesting things from the network + // and things required for us to stay in good repute + // with our P2P peers. + } else if let Some(item) = api_request_p1_queue.pop() { + Some(item) + // Handle backfill sync chain segments. + } else if let Some(item) = backfill_chain_segment.pop() { + Some(item) + // Handle light client requests. + } else if let Some(item) = lc_gossip_finality_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_gossip_optimistic_update_queue.pop() { + Some(item) + } else if let Some(item) = unknown_light_client_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_bootstrap_queue.pop() { + Some(item) + } else if let Some(item) = lc_rpc_optimistic_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_rpc_finality_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_update_range_queue.pop() { + Some(item) + // This statement should always be the final else statement. + } else { + // Let the journal know that a worker is freed and there's nothing else + // for it to do. + if let Some(work_journal_tx) = &work_journal_tx { + // We don't care if this message was successfully sent, we only use the journal + // during testing. + let _ = work_journal_tx.try_send(NOTHING_TO_DO); + } + None + }; if let Some(work_event) = work_event { let work_type = work_event.to_type(); @@ -1285,9 +1284,8 @@ impl BeaconProcessor { // I cannot see any good reason why this would happen. None => { warn!( - self.log, - "Unexpected gossip processor condition"; - "msg" => "no new work and cannot spawn worker" + msg = "no new work and cannot spawn worker", + "Unexpected gossip processor condition" ); None } @@ -1302,10 +1300,9 @@ impl BeaconProcessor { &[work_id], ); trace!( - self.log, - "Gossip processor skipping work"; - "msg" => "chain is syncing", - "work_id" => work_id + msg = "chain is syncing", + work_id = work_id, + "Gossip processor skipping work" ); None } @@ -1324,89 +1321,75 @@ impl BeaconProcessor { // Attestation batches are formed internally within the // `BeaconProcessor`, they are not sent from external services. Work::GossipAttestationBatch { .. } => crit!( - self.log, - "Unsupported inbound event"; - "type" => "GossipAttestationBatch" + work_type = "GossipAttestationBatch", + "Unsupported inbound event" ), Work::GossipAggregate { .. } => aggregate_queue.push(work), // Aggregate batches are formed internally within the `BeaconProcessor`, // they are not sent from external services. - Work::GossipAggregateBatch { .. } => crit!( - self.log, - "Unsupported inbound event"; - "type" => "GossipAggregateBatch" - ), - Work::GossipBlock { .. } => { - gossip_block_queue.push(work, work_id, &self.log) - } - Work::GossipBlobSidecar { .. } => { - gossip_blob_queue.push(work, work_id, &self.log) + Work::GossipAggregateBatch { .. } => { + crit!( + work_type = "GossipAggregateBatch", + "Unsupported inbound event" + ) } + Work::GossipBlock { .. } => gossip_block_queue.push(work, work_id), + Work::GossipBlobSidecar { .. } => gossip_blob_queue.push(work, work_id), Work::GossipDataColumnSidecar { .. } => { - gossip_data_column_queue.push(work, work_id, &self.log) + gossip_data_column_queue.push(work, work_id) } Work::DelayedImportBlock { .. } => { - delayed_block_queue.push(work, work_id, &self.log) + delayed_block_queue.push(work, work_id) } Work::GossipVoluntaryExit { .. } => { - gossip_voluntary_exit_queue.push(work, work_id, &self.log) + gossip_voluntary_exit_queue.push(work, work_id) } Work::GossipProposerSlashing { .. } => { - gossip_proposer_slashing_queue.push(work, work_id, &self.log) + gossip_proposer_slashing_queue.push(work, work_id) } Work::GossipAttesterSlashing { .. } => { - gossip_attester_slashing_queue.push(work, work_id, &self.log) + gossip_attester_slashing_queue.push(work, work_id) } Work::GossipSyncSignature { .. } => sync_message_queue.push(work), Work::GossipSyncContribution { .. } => { sync_contribution_queue.push(work) } Work::GossipLightClientFinalityUpdate { .. } => { - finality_update_queue.push(work, work_id, &self.log) + lc_gossip_finality_update_queue.push(work, work_id) } Work::GossipLightClientOptimisticUpdate { .. } => { - optimistic_update_queue.push(work, work_id, &self.log) + lc_gossip_optimistic_update_queue.push(work, work_id) } Work::RpcBlock { .. } | Work::IgnoredRpcBlock { .. } => { - rpc_block_queue.push(work, work_id, &self.log) + rpc_block_queue.push(work, work_id) } - Work::RpcBlobs { .. } => rpc_blob_queue.push(work, work_id, &self.log), + Work::RpcBlobs { .. } => rpc_blob_queue.push(work, work_id), Work::RpcCustodyColumn { .. } => { - rpc_custody_column_queue.push(work, work_id, &self.log) + rpc_custody_column_queue.push(work, work_id) } Work::RpcVerifyDataColumn(_) => { - rpc_verify_data_column_queue.push(work, work_id, &self.log) - } - Work::SamplingResult(_) => { - sampling_result_queue.push(work, work_id, &self.log) - } - Work::ChainSegment { .. } => { - chain_segment_queue.push(work, work_id, &self.log) + rpc_verify_data_column_queue.push(work, work_id) } + Work::SamplingResult(_) => sampling_result_queue.push(work, work_id), + Work::ChainSegment { .. } => chain_segment_queue.push(work, work_id), Work::ChainSegmentBackfill { .. } => { - backfill_chain_segment.push(work, work_id, &self.log) - } - Work::Status { .. } => status_queue.push(work, work_id, &self.log), - Work::BlocksByRangeRequest { .. } => { - bbrange_queue.push(work, work_id, &self.log) - } - Work::BlocksByRootsRequest { .. } => { - bbroots_queue.push(work, work_id, &self.log) - } - Work::BlobsByRangeRequest { .. } => { - blbrange_queue.push(work, work_id, &self.log) + backfill_chain_segment.push(work, work_id) } + Work::Status { .. } => status_queue.push(work, work_id), + Work::BlocksByRangeRequest { .. } => bbrange_queue.push(work, work_id), + Work::BlocksByRootsRequest { .. } => bbroots_queue.push(work, work_id), + Work::BlobsByRangeRequest { .. } => blbrange_queue.push(work, work_id), Work::LightClientBootstrapRequest { .. } => { - lc_bootstrap_queue.push(work, work_id, &self.log) + lc_bootstrap_queue.push(work, work_id) } Work::LightClientOptimisticUpdateRequest { .. } => { - lc_optimistic_update_queue.push(work, work_id, &self.log) + lc_rpc_optimistic_update_queue.push(work, work_id) } Work::LightClientFinalityUpdateRequest { .. } => { - lc_finality_update_queue.push(work, work_id, &self.log) + lc_rpc_finality_update_queue.push(work, work_id) } Work::LightClientUpdatesByRangeRequest { .. } => { - lc_update_range_queue.push(work, work_id, &self.log) + lc_update_range_queue.push(work, work_id) } Work::UnknownBlockAttestation { .. } => { unknown_block_attestation_queue.push(work) @@ -1415,29 +1398,23 @@ impl BeaconProcessor { unknown_block_aggregate_queue.push(work) } Work::GossipBlsToExecutionChange { .. } => { - gossip_bls_to_execution_change_queue.push(work, work_id, &self.log) - } - Work::BlobsByRootsRequest { .. } => { - blbroots_queue.push(work, work_id, &self.log) + gossip_bls_to_execution_change_queue.push(work, work_id) } + Work::BlobsByRootsRequest { .. } => blbroots_queue.push(work, work_id), Work::DataColumnsByRootsRequest { .. } => { - dcbroots_queue.push(work, work_id, &self.log) + dcbroots_queue.push(work, work_id) } Work::DataColumnsByRangeRequest { .. } => { - dcbrange_queue.push(work, work_id, &self.log) + dcbrange_queue.push(work, work_id) } Work::UnknownLightClientOptimisticUpdate { .. } => { - unknown_light_client_update_queue.push(work, work_id, &self.log) + unknown_light_client_update_queue.push(work, work_id) } Work::UnknownBlockSamplingRequest { .. } => { - unknown_block_sampling_request_queue.push(work, work_id, &self.log) - } - Work::ApiRequestP0 { .. } => { - api_request_p0_queue.push(work, work_id, &self.log) - } - Work::ApiRequestP1 { .. } => { - api_request_p1_queue.push(work, work_id, &self.log) + unknown_block_sampling_request_queue.push(work, work_id) } + Work::ApiRequestP0 { .. } => api_request_p0_queue.push(work, work_id), + Work::ApiRequestP1 { .. } => api_request_p1_queue.push(work, work_id), }; Some(work_type) } @@ -1472,9 +1449,11 @@ impl BeaconProcessor { WorkType::GossipAttesterSlashing => gossip_attester_slashing_queue.len(), WorkType::GossipSyncSignature => sync_message_queue.len(), WorkType::GossipSyncContribution => sync_contribution_queue.len(), - WorkType::GossipLightClientFinalityUpdate => finality_update_queue.len(), + WorkType::GossipLightClientFinalityUpdate => { + lc_gossip_finality_update_queue.len() + } WorkType::GossipLightClientOptimisticUpdate => { - optimistic_update_queue.len() + lc_gossip_optimistic_update_queue.len() } WorkType::RpcBlock => rpc_block_queue.len(), WorkType::RpcBlobs | WorkType::IgnoredRpcBlock => rpc_blob_queue.len(), @@ -1495,10 +1474,10 @@ impl BeaconProcessor { } WorkType::LightClientBootstrapRequest => lc_bootstrap_queue.len(), WorkType::LightClientOptimisticUpdateRequest => { - lc_optimistic_update_queue.len() + lc_rpc_optimistic_update_queue.len() } WorkType::LightClientFinalityUpdateRequest => { - lc_finality_update_queue.len() + lc_rpc_finality_update_queue.len() } WorkType::LightClientUpdatesByRangeRequest => lc_update_range_queue.len(), WorkType::ApiRequestP0 => api_request_p0_queue.len(), @@ -1513,19 +1492,17 @@ impl BeaconProcessor { if aggregate_queue.is_full() && aggregate_debounce.elapsed() { error!( - self.log, - "Aggregate attestation queue full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => aggregate_queue.max_length, + msg = "the system has insufficient resources for load", + queue_len = aggregate_queue.max_length, + "Aggregate attestation queue full" ) } if attestation_queue.is_full() && attestation_debounce.elapsed() { error!( - self.log, - "Attestation queue full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => attestation_queue.max_length, + msg = "the system has insufficient resources for load", + queue_len = attestation_queue.max_length, + "Attestation queue full" ) } } @@ -1556,7 +1533,6 @@ impl BeaconProcessor { let send_idle_on_drop = SendOnDrop { tx: idle_tx, _worker_timer: worker_timer, - log: self.log.clone(), }; let worker_id = self.current_workers; @@ -1565,10 +1541,9 @@ impl BeaconProcessor { let executor = self.executor.clone(); trace!( - self.log, - "Spawning beacon processor worker"; - "work" => work_id, - "worker" => worker_id, + work = work_id, + worker = worker_id, + "Spawning beacon processor worker" ); let task_spawner = TaskSpawner { @@ -1706,8 +1681,8 @@ impl TaskSpawner { } } -/// This struct will send a message on `self.tx` when it is dropped. An error will be logged on -/// `self.log` if the send fails (this happens when the node is shutting down). +/// This struct will send a message on `self.tx` when it is dropped. An error will be logged +/// if the send fails (this happens when the node is shutting down). /// /// ## Purpose /// @@ -1720,17 +1695,15 @@ pub struct SendOnDrop { tx: mpsc::Sender<()>, // The field is unused, but it's here to ensure the timer is dropped once the task has finished. _worker_timer: Option, - log: Logger, } impl Drop for SendOnDrop { fn drop(&mut self) { if let Err(e) = self.tx.try_send(()) { warn!( - self.log, - "Unable to free worker"; - "msg" => "did not free worker, shutdown may be underway", - "error" => %e + msg = "did not free worker, shutdown may be underway", + error = %e, + "Unable to free worker" ) } } diff --git a/beacon_node/beacon_processor/src/work_reprocessing_queue.rs b/beacon_node/beacon_processor/src/work_reprocessing_queue.rs index a43310ac83..a4f539aea0 100644 --- a/beacon_node/beacon_processor/src/work_reprocessing_queue.rs +++ b/beacon_node/beacon_processor/src/work_reprocessing_queue.rs @@ -16,8 +16,8 @@ use fnv::FnvHashMap; use futures::task::Poll; use futures::{Stream, StreamExt}; use itertools::Itertools; +use logging::crit; use logging::TimeLatch; -use slog::{crit, debug, error, trace, warn, Logger}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; use std::future::Future; @@ -29,6 +29,7 @@ use strum::AsRefStr; use task_executor::TaskExecutor; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio_util::time::delay_queue::{DelayQueue, Key as DelayKey}; +use tracing::{debug, error, trace, warn}; use types::{EthSpec, Hash256, Slot}; const TASK_NAME: &str = "beacon_processor_reprocess_queue"; @@ -374,7 +375,6 @@ pub fn spawn_reprocess_scheduler( work_reprocessing_rx: Receiver, executor: &TaskExecutor, slot_clock: Arc, - log: Logger, maximum_gossip_clock_disparity: Duration, ) -> Result<(), String> { // Sanity check @@ -386,14 +386,10 @@ pub fn spawn_reprocess_scheduler( executor.spawn( async move { while let Some(msg) = queue.next().await { - queue.handle_message(msg, &log); + queue.handle_message(msg); } - debug!( - log, - "Re-process queue stopped"; - "msg" => "shutting down" - ); + debug!(msg = "shutting down", "Re-process queue stopped"); }, TASK_NAME, ); @@ -436,7 +432,7 @@ impl ReprocessQueue { } } - fn handle_message(&mut self, msg: InboundEvent, log: &Logger) { + fn handle_message(&mut self, msg: InboundEvent) { use ReprocessQueueMessage::*; match msg { // Some block has been indicated as "early" and should be processed when the @@ -455,10 +451,9 @@ impl ReprocessQueue { if self.queued_gossip_block_roots.len() >= MAXIMUM_QUEUED_BLOCKS { if self.early_block_debounce.elapsed() { warn!( - log, - "Early blocks queue is full"; - "queue_size" => MAXIMUM_QUEUED_BLOCKS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_BLOCKS, + msg = "check system clock", + "Early blocks queue is full" ); } // Drop the block. @@ -490,10 +485,7 @@ impl ReprocessQueue { .try_send(ReadyWork::Block(early_block)) .is_err() { - error!( - log, - "Failed to send block"; - ); + error!("Failed to send block"); } } } @@ -507,10 +499,9 @@ impl ReprocessQueue { if self.rpc_block_delay_queue.len() >= MAXIMUM_QUEUED_BLOCKS { if self.rpc_block_debounce.elapsed() { warn!( - log, - "RPC blocks queue is full"; - "queue_size" => MAXIMUM_QUEUED_BLOCKS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_BLOCKS, + msg = "check system clock", + "RPC blocks queue is full" ); } // Return the block to the beacon processor signalling to @@ -522,10 +513,7 @@ impl ReprocessQueue { })) .is_err() { - error!( - log, - "Failed to send rpc block to beacon processor"; - ); + error!("Failed to send rpc block to beacon processor"); } return; } @@ -536,29 +524,24 @@ impl ReprocessQueue { } InboundEvent::ReadyRpcBlock(queued_rpc_block) => { debug!( - log, - "Sending rpc block for reprocessing"; - "block_root" => %queued_rpc_block.beacon_block_root + %queued_rpc_block.beacon_block_root, + "Sending rpc block for reprocessing" ); if self .ready_work_tx .try_send(ReadyWork::RpcBlock(queued_rpc_block)) .is_err() { - error!( - log, - "Failed to send rpc block to beacon processor"; - ); + error!("Failed to send rpc block to beacon processor"); } } InboundEvent::Msg(UnknownBlockAggregate(queued_aggregate)) => { if self.attestations_delay_queue.len() >= MAXIMUM_QUEUED_ATTESTATIONS { if self.attestation_delay_debounce.elapsed() { error!( - log, - "Aggregate attestation delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_ATTESTATIONS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_ATTESTATIONS, + msg = "check system clock", + "Aggregate attestation delay queue is full" ); } // Drop the attestation. @@ -588,10 +571,9 @@ impl ReprocessQueue { if self.attestations_delay_queue.len() >= MAXIMUM_QUEUED_ATTESTATIONS { if self.attestation_delay_debounce.elapsed() { error!( - log, - "Attestation delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_ATTESTATIONS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_ATTESTATIONS, + msg = "check system clock", + "Attestation delay queue is full" ); } // Drop the attestation. @@ -623,10 +605,9 @@ impl ReprocessQueue { if self.lc_updates_delay_queue.len() >= MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES { if self.lc_update_delay_debounce.elapsed() { error!( - log, - "Light client updates delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES, + msg = "check system clock", + "Light client updates delay queue is full" ); } // Drop the light client update. @@ -658,9 +639,8 @@ impl ReprocessQueue { if self.sampling_requests_delay_queue.len() >= MAXIMUM_QUEUED_SAMPLING_REQUESTS { if self.sampling_request_delay_debounce.elapsed() { error!( - log, - "Sampling requests delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_SAMPLING_REQUESTS, + queue_size = MAXIMUM_QUEUED_SAMPLING_REQUESTS, + "Sampling requests delay queue is full" ); } // Drop the inbound message. @@ -724,23 +704,21 @@ impl ReprocessQueue { // There is a mismatch between the attestation ids registered for this // root and the queued attestations. This should never happen. error!( - log, - "Unknown queued attestation for block root"; - "block_root" => ?block_root, - "att_id" => ?id, + ?block_root, + att_id = ?id, + "Unknown queued attestation for block root" ); } } if failed_to_send_count > 0 { error!( - log, - "Ignored scheduled attestation(s) for block"; - "hint" => "system may be overloaded", - "parent_root" => ?parent_root, - "block_root" => ?block_root, - "failed_count" => failed_to_send_count, - "sent_count" => sent_count, + hint = "system may be overloaded", + ?parent_root, + ?block_root, + failed_count = failed_to_send_count, + sent_count, + "Ignored scheduled attestation(s) for block" ); } } @@ -772,18 +750,17 @@ impl ReprocessQueue { } } else { // This should never happen. - error!(log, "Unknown sampling request for block root"; "block_root" => ?block_root, "id" => ?id); + error!(?block_root, ?id, "Unknown sampling request for block root"); } } if failed_to_send_count > 0 { error!( - log, - "Ignored scheduled sampling requests for block"; - "hint" => "system may be overloaded", - "block_root" => ?block_root, - "failed_count" => failed_to_send_count, - "sent_count" => sent_count, + hint = "system may be overloaded", + ?block_root, + failed_to_send_count, + sent_count, + "Ignored scheduled sampling requests for block" ); } } @@ -795,10 +772,9 @@ impl ReprocessQueue { .remove(&parent_root) { debug!( - log, - "Dequeuing light client optimistic updates"; - "parent_root" => %parent_root, - "count" => queued_lc_id.len(), + %parent_root, + count = queued_lc_id.len(), + "Dequeuing light client optimistic updates" ); for lc_id in queued_lc_id { @@ -818,23 +794,16 @@ impl ReprocessQueue { // Send the work match self.ready_work_tx.try_send(work) { - Ok(_) => trace!( - log, - "reprocessing light client update sent"; - ), - Err(_) => error!( - log, - "Failed to send scheduled light client update"; - ), + Ok(_) => trace!("reprocessing light client update sent"), + Err(_) => error!("Failed to send scheduled light client update"), } } else { // There is a mismatch between the light client update ids registered for this // root and the queued light client updates. This should never happen. error!( - log, - "Unknown queued light client update for parent root"; - "parent_root" => ?parent_root, - "lc_id" => ?lc_id, + ?parent_root, + ?lc_id, + "Unknown queued light client update for parent root" ); } } @@ -855,11 +824,7 @@ impl ReprocessQueue { if !self.queued_gossip_block_roots.remove(&block_root) { // Log an error to alert that we've made a bad assumption about how this // program works, but still process the block anyway. - error!( - log, - "Unknown block in delay queue"; - "block_root" => ?block_root - ); + error!(?block_root, "Unknown block in delay queue"); } if self @@ -867,10 +832,7 @@ impl ReprocessQueue { .try_send(ReadyWork::Block(ready_block)) .is_err() { - error!( - log, - "Failed to pop queued block"; - ); + error!("Failed to pop queued block"); } } InboundEvent::ReadyAttestation(queued_id) => { @@ -901,10 +863,9 @@ impl ReprocessQueue { } { if self.ready_work_tx.try_send(work).is_err() { error!( - log, - "Ignored scheduled attestation"; - "hint" => "system may be overloaded", - "beacon_block_root" => ?root + hint = "system may be overloaded", + beacon_block_root = ?root, + "Ignored scheduled attestation" ); } @@ -929,10 +890,7 @@ impl ReprocessQueue { }, ) { if self.ready_work_tx.try_send(work).is_err() { - error!( - log, - "Failed to send scheduled light client optimistic update"; - ); + error!("Failed to send scheduled light client optimistic update"); } if let Some(queued_lc_updates) = self @@ -955,11 +913,7 @@ impl ReprocessQueue { duration.as_millis().to_string() }); - debug!( - log, - "Sending scheduled backfill work"; - "millis_from_slot_start" => millis_from_slot_start - ); + debug!(%millis_from_slot_start, "Sending scheduled backfill work"); match self .ready_work_tx @@ -971,9 +925,8 @@ impl ReprocessQueue { Err(mpsc::error::TrySendError::Full(ReadyWork::BackfillSync(batch))) | Err(mpsc::error::TrySendError::Closed(ReadyWork::BackfillSync(batch))) => { error!( - log, - "Failed to send scheduled backfill work"; - "info" => "sending work back to queue" + info = "sending work back to queue", + "Failed to send scheduled backfill work" ); self.queued_backfill_batches.insert(0, batch); @@ -984,10 +937,7 @@ impl ReprocessQueue { } // The message was not sent and we didn't get the correct // return result. This is a logic error. - _ => crit!( - log, - "Unexpected return from try_send error"; - ), + _ => crit!("Unexpected return from try_send error"), } } } @@ -1057,7 +1007,7 @@ impl ReprocessQueue { #[cfg(test)] mod tests { use super::*; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use slot_clock::{ManualSlotClock, TestingSlotClock}; use std::ops::Add; use std::sync::Arc; @@ -1105,8 +1055,8 @@ mod tests { // See: https://github.com/sigp/lighthouse/issues/5504#issuecomment-2050930045 #[tokio::test] async fn backfill_schedule_failed_should_reschedule() { + create_test_tracing_subscriber(); let runtime = TestRuntime::default(); - let log = test_logger(); let (work_reprocessing_tx, work_reprocessing_rx) = mpsc::channel(1); let (ready_work_tx, mut ready_work_rx) = mpsc::channel(1); let slot_duration = 12; @@ -1117,7 +1067,6 @@ mod tests { work_reprocessing_rx, &runtime.task_executor, slot_clock.clone(), - log, Duration::from_millis(500), ) .unwrap(); diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index 5f64ac7e43..6d82542cef 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -29,6 +29,11 @@ pub const DEFAULT_GET_HEADER_TIMEOUT_MILLIS: u64 = 1000; /// Default user agent for HTTP requests. pub const DEFAULT_USER_AGENT: &str = lighthouse_version::VERSION; +/// The value we set on the `ACCEPT` http header to indicate a preference for ssz response. +pub const PREFERENCE_ACCEPT_VALUE: &str = "application/octet-stream;q=1.0,application/json;q=0.9"; +/// Only accept json responses. +pub const JSON_ACCEPT_VALUE: &str = "application/json"; + #[derive(Clone)] pub struct Timeouts { get_header: Duration, @@ -57,7 +62,12 @@ pub struct BuilderHttpClient { server: SensitiveUrl, timeouts: Timeouts, user_agent: String, - ssz_enabled: Arc, + /// Only use json for all requests/responses types. + disable_ssz: bool, + /// Indicates that the `get_header` response had content-type ssz + /// so we can set content-type header to ssz to make the `submit_blinded_blocks` + /// request. + ssz_available: Arc, } impl BuilderHttpClient { @@ -65,6 +75,7 @@ impl BuilderHttpClient { server: SensitiveUrl, user_agent: Option, builder_header_timeout: Option, + disable_ssz: bool, ) -> Result { let user_agent = user_agent.unwrap_or(DEFAULT_USER_AGENT.to_string()); let client = reqwest::Client::builder().user_agent(&user_agent).build()?; @@ -73,7 +84,8 @@ impl BuilderHttpClient { server, timeouts: Timeouts::new(builder_header_timeout), user_agent, - ssz_enabled: Arc::new(false.into()), + disable_ssz, + ssz_available: Arc::new(false.into()), }) } @@ -124,7 +136,7 @@ impl BuilderHttpClient { let Ok(Some(fork_name)) = self.fork_name_from_header(&headers) else { // if no fork version specified, attempt to fallback to JSON - self.ssz_enabled.store(false, Ordering::SeqCst); + self.ssz_available.store(false, Ordering::SeqCst); return serde_json::from_slice(&response_bytes).map_err(Error::InvalidJson); }; @@ -132,7 +144,7 @@ impl BuilderHttpClient { match content_type { ContentType::Ssz => { - self.ssz_enabled.store(true, Ordering::SeqCst); + self.ssz_available.store(true, Ordering::SeqCst); T::from_ssz_bytes_by_fork(&response_bytes, fork_name) .map(|data| ForkVersionedResponse { version: Some(fork_name), @@ -142,15 +154,17 @@ impl BuilderHttpClient { .map_err(Error::InvalidSsz) } ContentType::Json => { - self.ssz_enabled.store(false, Ordering::SeqCst); + self.ssz_available.store(false, Ordering::SeqCst); serde_json::from_slice(&response_bytes).map_err(Error::InvalidJson) } } } /// Return `true` if the most recently received response from the builder had SSZ Content-Type. - pub fn is_ssz_enabled(&self) -> bool { - self.ssz_enabled.load(Ordering::SeqCst) + /// Return `false` otherwise. + /// Also returns `false` if we have explicitly disabled ssz. + pub fn is_ssz_available(&self) -> bool { + !self.disable_ssz && self.ssz_available.load(Ordering::SeqCst) } async fn get_with_timeout( @@ -213,7 +227,7 @@ impl BuilderHttpClient { &self, url: U, ssz_body: Vec, - mut headers: HeaderMap, + headers: HeaderMap, timeout: Option, ) -> Result { let mut builder = self.client.post(url); @@ -221,11 +235,6 @@ impl BuilderHttpClient { builder = builder.timeout(timeout); } - headers.insert( - CONTENT_TYPE_HEADER, - HeaderValue::from_static(SSZ_CONTENT_TYPE_HEADER), - ); - let response = builder .headers(headers) .body(ssz_body) @@ -292,9 +301,21 @@ impl BuilderHttpClient { .push("blinded_blocks"); let mut headers = HeaderMap::new(); - if let Ok(value) = HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) { - headers.insert(CONSENSUS_VERSION_HEADER, value); - } + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(SSZ_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); let result = self .post_ssz_with_raw_response( @@ -326,9 +347,21 @@ impl BuilderHttpClient { .push("blinded_blocks"); let mut headers = HeaderMap::new(); - if let Ok(value) = HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) { - headers.insert(CONSENSUS_VERSION_HEADER, value); - } + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(JSON_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); Ok(self .post_with_raw_response( @@ -362,12 +395,20 @@ impl BuilderHttpClient { .push(pubkey.as_hex_string().as_str()); let mut headers = HeaderMap::new(); - if let Ok(ssz_content_type_header) = HeaderValue::from_str(&format!( - "{}; q=1.0,{}; q=0.9", - SSZ_CONTENT_TYPE_HEADER, JSON_CONTENT_TYPE_HEADER - )) { - headers.insert(ACCEPT, ssz_content_type_header); - }; + if self.disable_ssz { + headers.insert( + ACCEPT, + HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + } else { + // Indicate preference for ssz response in the accept header + headers.insert( + ACCEPT, + HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + } let resp = self .get_with_header(path, self.timeouts.get_header, headers) @@ -395,3 +436,18 @@ impl BuilderHttpClient { .await } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_headers_no_panic() { + for fork in ForkName::list_all() { + assert!(HeaderValue::from_str(&fork.to_string()).is_ok()); + } + assert!(HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE).is_ok()); + assert!(HeaderValue::from_str(JSON_ACCEPT_VALUE).is_ok()); + assert!(HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER).is_ok()); + } +} diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 614115eb58..e11fc23072 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -27,6 +27,7 @@ http_api = { workspace = true } http_metrics = { path = "../http_metrics" } kzg = { workspace = true } lighthouse_network = { workspace = true } +logging = { workspace = true } metrics = { workspace = true } monitoring_api = { workspace = true } network = { workspace = true } @@ -35,11 +36,12 @@ serde = { workspace = true } serde_json = { workspace = true } slasher = { workspace = true } slasher_service = { path = "../../slasher/service" } -slog = { workspace = true } slot_clock = { workspace = true } store = { workspace = true } task_executor = { workspace = true } time = "0.3.5" timer = { path = "../timer" } tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index e3bfd60a48..c8ff6521c8 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -35,7 +35,6 @@ use monitoring_api::{MonitoringHttpClient, ProcessType}; use network::{NetworkConfig, NetworkSenders, NetworkService}; use slasher::Slasher; use slasher_service::SlasherService; -use slog::{debug, info, warn, Logger}; use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -44,6 +43,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use store::database::interface::BeaconNodeBackend; use timer::spawn_timer; use tokio::sync::oneshot; +use tracing::{debug, info, warn}; use types::{ test_utils::generate_deterministic_keypairs, BeaconState, BlobSidecarList, ChainSpec, EthSpec, ExecutionBlockHash, Hash256, SignedBeaconBlock, @@ -170,11 +170,9 @@ where let runtime_context = runtime_context.ok_or("beacon_chain_start_method requires a runtime context")?; let context = runtime_context.service_context("beacon".into()); - let log = context.log(); let spec = chain_spec.ok_or("beacon_chain_start_method requires a chain spec")?; let event_handler = if self.http_api_config.enabled { Some(ServerSentEventHandler::new( - context.log().clone(), self.http_api_config.sse_capacity_multiplier, )) } else { @@ -183,12 +181,8 @@ where let execution_layer = if let Some(config) = config.execution_layer.clone() { let context = runtime_context.service_context("exec".into()); - let execution_layer = ExecutionLayer::from_config( - config, - context.executor.clone(), - context.log().clone(), - ) - .map_err(|e| format!("unable to start execution layer endpoints: {:?}", e))?; + let execution_layer = ExecutionLayer::from_config(config, context.executor.clone()) + .map_err(|e| format!("unable to start execution layer endpoints: {:?}", e))?; Some(execution_layer) } else { None @@ -205,7 +199,6 @@ where }; let builder = BeaconChainBuilder::new(eth_spec_instance, Arc::new(kzg)) - .logger(context.log().clone()) .store(store) .task_executor(context.executor.clone()) .custom_spec(spec.clone()) @@ -245,7 +238,7 @@ where // using it. let client_genesis = if matches!(client_genesis, ClientGenesis::FromStore) && !chain_exists { - info!(context.log(), "Defaulting to deposit contract genesis"); + info!("Defaulting to deposit contract genesis"); ClientGenesis::DepositContract } else if chain_exists { @@ -253,9 +246,8 @@ where || matches!(client_genesis, ClientGenesis::CheckpointSyncUrl { .. }) { info!( - context.log(), - "Refusing to checkpoint sync"; - "msg" => "database already exists, use --purge-db to force checkpoint sync" + msg = "database already exists, use --purge-db to force checkpoint sync", + "Refusing to checkpoint sync" ); } @@ -295,12 +287,9 @@ where builder.genesis_state(genesis_state).map(|v| (v, None))? } ClientGenesis::GenesisState => { - info!( - context.log(), - "Starting from known genesis state"; - ); + info!("Starting from known genesis state"); - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; // If the user has not explicitly allowed genesis sync, prevent // them from trying to sync from genesis if we're outside of the @@ -348,12 +337,9 @@ where anchor_block_bytes, anchor_blobs_bytes, } => { - info!(context.log(), "Starting checkpoint sync"); + info!("Starting checkpoint sync"); if config.chain.genesis_backfill { - info!( - context.log(), - "Blocks will downloaded all the way back to genesis" - ); + info!("Blocks will downloaded all the way back to genesis"); } let anchor_state = BeaconState::from_ssz_bytes(&anchor_state_bytes, &spec) @@ -371,7 +357,7 @@ where } else { None }; - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; builder .weak_subjectivity_state( @@ -384,15 +370,11 @@ where } ClientGenesis::CheckpointSyncUrl { url } => { info!( - context.log(), - "Starting checkpoint sync"; - "remote_url" => %url, + remote_url = %url, + "Starting checkpoint sync" ); if config.chain.genesis_backfill { - info!( - context.log(), - "Blocks will be downloaded all the way back to genesis" - ); + info!("Blocks will be downloaded all the way back to genesis"); } let remote = BeaconNodeHttpClient::new( @@ -406,7 +388,7 @@ where // We want to fetch deposit snapshot before fetching the finalized beacon state to // ensure that the snapshot is not newer than the beacon state that satisfies the // deposit finalization conditions - debug!(context.log(), "Downloading deposit snapshot"); + debug!("Downloading deposit snapshot"); let deposit_snapshot_result = remote .get_deposit_snapshot() .await @@ -423,22 +405,18 @@ where if deposit_snapshot.is_valid() { Some(deposit_snapshot) } else { - warn!(context.log(), "Remote BN sent invalid deposit snapshot!"); + warn!("Remote BN sent invalid deposit snapshot!"); None } } Ok(None) => { - warn!( - context.log(), - "Remote BN does not support EIP-4881 fast deposit sync" - ); + warn!("Remote BN does not support EIP-4881 fast deposit sync"); None } Err(e) => { warn!( - context.log(), - "Remote BN does not support EIP-4881 fast deposit sync"; - "error" => e + error = e, + "Remote BN does not support EIP-4881 fast deposit sync" ); None } @@ -447,21 +425,18 @@ where None }; - debug!( - context.log(), - "Downloading finalized state"; - ); + debug!("Downloading finalized state"); let state = remote .get_debug_beacon_states_ssz::(StateId::Finalized, &spec) .await .map_err(|e| format!("Error loading checkpoint state from remote: {:?}", e))? .ok_or_else(|| "Checkpoint state missing from remote".to_string())?; - debug!(context.log(), "Downloaded finalized state"; "slot" => ?state.slot()); + debug!(slot = ?state.slot(), "Downloaded finalized state"); let finalized_block_slot = state.latest_block_header().slot; - debug!(context.log(), "Downloading finalized block"; "block_slot" => ?finalized_block_slot); + debug!(block_slot = ?finalized_block_slot,"Downloading finalized block"); let block = remote .get_beacon_blocks_ssz::(BlockId::Slot(finalized_block_slot), &spec) .await @@ -476,24 +451,23 @@ where .ok_or("Finalized block missing from remote, it returned 404")?; let block_root = block.canonical_root(); - debug!(context.log(), "Downloaded finalized block"); + debug!("Downloaded finalized block"); let blobs = if block.message().body().has_blobs() { - debug!(context.log(), "Downloading finalized blobs"); + debug!("Downloading finalized blobs"); if let Some(response) = remote .get_blobs::(BlockId::Root(block_root), None) .await .map_err(|e| format!("Error fetching finalized blobs from remote: {e:?}"))? { - debug!(context.log(), "Downloaded finalized blobs"); + debug!("Downloaded finalized blobs"); Some(response.data) } else { warn!( - context.log(), - "Checkpoint server is missing blobs"; - "block_root" => %block_root, - "hint" => "use a different URL or ask the provider to update", - "impact" => "db will be slightly corrupt until these blobs are pruned", + block_root = %block_root, + hint = "use a different URL or ask the provider to update", + impact = "db will be slightly corrupt until these blobs are pruned", + "Checkpoint server is missing blobs" ); None } @@ -501,35 +475,31 @@ where None }; - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; info!( - context.log(), - "Loaded checkpoint block and state"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), - "block_root" => ?block_root, + block_slot = %block.slot(), + state_slot = %state.slot(), + block_root = ?block_root, + "Loaded checkpoint block and state" ); let service = deposit_snapshot.and_then(|snapshot| match Eth1Service::from_deposit_snapshot( config.eth1, - context.log().clone(), spec.clone(), &snapshot, ) { Ok(service) => { info!( - context.log(), - "Loaded deposit tree snapshot"; - "deposits loaded" => snapshot.deposit_count, + deposits_loaded = snapshot.deposit_count, + "Loaded deposit tree snapshot" ); Some(service) } Err(e) => { - warn!(context.log(), - "Unable to load deposit snapshot"; - "error" => ?e + warn!(error = ?e, + "Unable to load deposit snapshot" ); None } @@ -541,18 +511,14 @@ where } ClientGenesis::DepositContract => { info!( - context.log(), - "Waiting for eth2 genesis from eth1"; - "eth1_endpoints" => format!("{:?}", &config.eth1.endpoint), - "contract_deploy_block" => config.eth1.deposit_contract_deploy_block, - "deposit_contract" => &config.eth1.deposit_contract_address + eth1_endpoints = ?config.eth1.endpoint, + contract_deploy_block = config.eth1.deposit_contract_deploy_block, + deposit_contract = &config.eth1.deposit_contract_address, + "Waiting for eth2 genesis from eth1" ); - let genesis_service = Eth1GenesisService::new( - config.eth1, - context.log().clone(), - context.eth2_config().spec.clone(), - )?; + let genesis_service = + Eth1GenesisService::new(config.eth1, context.eth2_config().spec.clone())?; // If the HTTP API server is enabled, start an instance of it where it only // contains a reference to the eth1 service (all non-eth1 endpoints will fail @@ -575,7 +541,6 @@ where beacon_processor_send: None, beacon_processor_reprocess_send: None, eth1_service: Some(genesis_service.eth1_service.clone()), - log: context.log().clone(), sse_logging_components: runtime_context.sse_logging_components.clone(), }); @@ -587,10 +552,9 @@ where let (listen_addr, server) = http_api::serve(ctx, exit_future) .map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?; - let log_clone = context.log().clone(); let http_api_task = async move { server.await; - debug!(log_clone, "HTTP API server task ended"); + debug!("HTTP API server task ended"); }; context @@ -617,9 +581,8 @@ where // We will restart it again after we've finished setting up for genesis. while TcpListener::bind(http_listen).is_err() { warn!( - context.log(), - "Waiting for HTTP server port to open"; - "port" => http_listen + port = %http_listen, + "Waiting for HTTP server port to open" ); tokio::time::sleep(Duration::from_secs(1)).await; } @@ -738,7 +701,7 @@ where .as_ref() .ok_or("monitoring_client requires a runtime_context")? .service_context("monitoring_client".into()); - let monitoring_client = MonitoringHttpClient::new(config, context.log().clone())?; + let monitoring_client = MonitoringHttpClient::new(config)?; monitoring_client.auto_update( context.executor, vec![ProcessType::BeaconNode, ProcessType::System], @@ -798,7 +761,6 @@ where .beacon_processor_config .take() .ok_or("build requires a beacon_processor_config")?; - let log = runtime_context.log().clone(); let http_api_listen_addr = if self.http_api_config.enabled { let ctx = Arc::new(http_api::Context { @@ -812,7 +774,6 @@ where beacon_processor_channels.work_reprocessing_tx.clone(), ), sse_logging_components: runtime_context.sse_logging_components.clone(), - log: log.clone(), }); let exit = runtime_context.executor.exit(); @@ -820,10 +781,9 @@ where let (listen_addr, server) = http_api::serve(ctx, exit) .map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?; - let http_log = runtime_context.log().clone(); let http_api_task = async move { server.await; - debug!(http_log, "HTTP API server task ended"); + debug!("HTTP API server task ended"); }; runtime_context @@ -833,7 +793,7 @@ where Some(listen_addr) } else { - info!(log, "HTTP server is disabled"); + info!("HTTP server is disabled"); None }; @@ -844,7 +804,6 @@ where db_path: self.db_path.clone(), freezer_db_path: self.freezer_db_path.clone(), gossipsub_registry: self.libp2p_registry.take().map(std::sync::Mutex::new), - log: log.clone(), }); let exit = runtime_context.executor.exit(); @@ -858,7 +817,7 @@ where Some(listen_addr) } else { - debug!(log, "Metrics server is disabled"); + debug!("Metrics server is disabled"); None }; @@ -874,7 +833,6 @@ where executor: beacon_processor_context.executor.clone(), current_workers: 0, config: beacon_processor_config, - log: beacon_processor_context.log().clone(), } .spawn_manager( beacon_processor_channels.beacon_processor_rx, @@ -895,12 +853,7 @@ where } let state_advance_context = runtime_context.service_context("state_advance".into()); - let state_advance_log = state_advance_context.log().clone(); - spawn_state_advance_timer( - state_advance_context.executor, - beacon_chain.clone(), - state_advance_log, - ); + spawn_state_advance_timer(state_advance_context.executor, beacon_chain.clone()); if let Some(execution_layer) = beacon_chain.execution_layer.as_ref() { // Only send a head update *after* genesis. @@ -929,9 +882,8 @@ where // node comes online. if let Err(e) = result { warn!( - log, - "Failed to update head on execution engines"; - "error" => ?e + error = ?e, + "Failed to update head on execution engines" ); } }, @@ -954,14 +906,12 @@ where let inner_chain = beacon_chain.clone(); let light_client_update_context = runtime_context.service_context("lc_update".to_string()); - let log = light_client_update_context.log().clone(); light_client_update_context.executor.spawn( async move { compute_light_client_updates( &inner_chain, light_client_server_rv, beacon_processor_channels.work_reprocessing_tx, - &log, ) .await }, @@ -1044,7 +994,6 @@ where cold_path: &Path, blobs_path: &Path, config: StoreConfig, - log: Logger, ) -> Result { let context = self .runtime_context @@ -1073,7 +1022,6 @@ where genesis_state_root, from, to, - log, ) }; @@ -1084,7 +1032,6 @@ where schema_upgrade, config, spec, - context.log().clone(), ) .map_err(|e| format!("Unable to open database: {:?}", e))?; self.store = Some(store); @@ -1132,22 +1079,15 @@ where CachingEth1Backend::from_service(eth1_service_from_genesis) } else if config.purge_cache { - CachingEth1Backend::new(config, context.log().clone(), spec)? + CachingEth1Backend::new(config, spec)? } else { beacon_chain_builder .get_persisted_eth1_backend()? .map(|persisted| { - Eth1Chain::from_ssz_container( - &persisted, - config.clone(), - &context.log().clone(), - spec.clone(), - ) - .map(|chain| chain.into_backend()) + Eth1Chain::from_ssz_container(&persisted, config.clone(), spec.clone()) + .map(|chain| chain.into_backend()) }) - .unwrap_or_else(|| { - CachingEth1Backend::new(config, context.log().clone(), spec.clone()) - })? + .unwrap_or_else(|| CachingEth1Backend::new(config, spec.clone()))? }; self.eth1_service = Some(backend.core.clone()); @@ -1230,7 +1170,6 @@ where async fn genesis_state( context: &RuntimeContext, config: &ClientConfig, - log: &Logger, ) -> Result, String> { let eth2_network_config = context .eth2_network_config @@ -1240,7 +1179,6 @@ async fn genesis_state( .genesis_state::( config.genesis_state_url.as_deref(), config.genesis_state_url_timeout, - log, ) .await? .ok_or_else(|| "Genesis state is unknown".to_string()) diff --git a/beacon_node/client/src/compute_light_client_updates.rs b/beacon_node/client/src/compute_light_client_updates.rs index 1eb977d421..fab284c428 100644 --- a/beacon_node/client/src/compute_light_client_updates.rs +++ b/beacon_node/client/src/compute_light_client_updates.rs @@ -2,8 +2,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes, LightClientProducerEvent}; use beacon_processor::work_reprocessing_queue::ReprocessQueueMessage; use futures::channel::mpsc::Receiver; use futures::StreamExt; -use slog::{error, Logger}; use tokio::sync::mpsc::Sender; +use tracing::error; // Each `LightClientProducerEvent` is ~200 bytes. With the light_client server producing only recent // updates it is okay to drop some events in case of overloading. In normal network conditions @@ -15,7 +15,6 @@ pub async fn compute_light_client_updates( chain: &BeaconChain, mut light_client_server_rv: Receiver>, reprocess_tx: Sender, - log: &Logger, ) { // Should only receive events for recent blocks, import_block filters by blocks close to clock. // @@ -28,12 +27,12 @@ pub async fn compute_light_client_updates( chain .recompute_and_cache_light_client_updates(event) .unwrap_or_else(|e| { - error!(log, "error computing light_client updates {:?}", e); + error!("error computing light_client updates {:?}", e); }); let msg = ReprocessQueueMessage::NewLightClientOptimisticUpdate { parent_root }; if reprocess_tx.try_send(msg).is_err() { - error!(log, "Failed to inform light client update"; "parent_root" => %parent_root) + error!(%parent_root,"Failed to inform light client update") }; } } diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 0c3b1578d6..d103d48dfb 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -8,12 +8,13 @@ use beacon_chain::{ BeaconChain, BeaconChainTypes, ExecutionStatus, }; use lighthouse_network::{types::SyncState, NetworkGlobals}; -use slog::{crit, debug, error, info, warn, Logger}; +use logging::crit; use slot_clock::SlotClock; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tokio::time::sleep; +use tracing::{debug, error, info, warn}; use types::*; /// Create a warning log whenever the peer count is at or below this value. @@ -39,7 +40,6 @@ pub fn spawn_notifier( let slot_duration = Duration::from_secs(seconds_per_slot); let speedo = Mutex::new(Speedo::default()); - let log = executor.log().clone(); // Keep track of sync state and reset the speedo on specific sync state changes. // Specifically, if we switch between a sync and a backfill sync, reset the speedo. @@ -56,15 +56,14 @@ pub fn spawn_notifier( // waiting for genesis. Some(next_slot) if next_slot > slot_duration => { info!( - log, - "Waiting for genesis"; - "peers" => peer_count_pretty(network.connected_peers()), - "wait_time" => estimated_time_pretty(Some(next_slot.as_secs() as f64)), + peers = peer_count_pretty(network.connected_peers()), + wait_time = estimated_time_pretty(Some(next_slot.as_secs() as f64)), + "Waiting for genesis" ); - eth1_logging(&beacon_chain, &log); - bellatrix_readiness_logging(Slot::new(0), &beacon_chain, &log).await; - capella_readiness_logging(Slot::new(0), &beacon_chain, &log).await; - genesis_execution_payload_logging(&beacon_chain, &log).await; + eth1_logging(&beacon_chain); + bellatrix_readiness_logging(Slot::new(0), &beacon_chain).await; + capella_readiness_logging(Slot::new(0), &beacon_chain).await; + genesis_execution_payload_logging(&beacon_chain).await; sleep(slot_duration).await; } _ => break, @@ -82,7 +81,7 @@ pub fn spawn_notifier( let wait = match beacon_chain.slot_clock.duration_to_next_slot() { Some(duration) => duration + slot_duration / 2, None => { - warn!(log, "Unable to read current slot"); + warn!("Unable to read current slot"); sleep(slot_duration).await; continue; } @@ -120,11 +119,7 @@ pub fn spawn_notifier( let current_slot = match beacon_chain.slot() { Ok(slot) => slot, Err(e) => { - error!( - log, - "Unable to read current slot"; - "error" => format!("{:?}", e) - ); + error!(error = ?e, "Unable to read current slot"); break; } }; @@ -168,26 +163,28 @@ pub fn spawn_notifier( ); if connected_peer_count <= WARN_PEER_COUNT { - warn!(log, "Low peer count"; "peer_count" => peer_count_pretty(connected_peer_count)); + warn!( + peer_count = peer_count_pretty(connected_peer_count), + "Low peer count" + ); } debug!( - log, - "Slot timer"; - "peers" => peer_count_pretty(connected_peer_count), - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "head_block" => format!("{}", head_root), - "head_slot" => head_slot, - "current_slot" => current_slot, - "sync_state" =>format!("{}", current_sync_state) + peers = peer_count_pretty(connected_peer_count), + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + head_block = %head_root, + %head_slot, + %current_slot, + sync_state = %current_sync_state, + "Slot timer" ); // Log if we are backfilling. let is_backfilling = matches!(current_sync_state, SyncState::BackFillSyncing { .. }); if is_backfilling && last_backfill_log_slot - .map_or(true, |slot| slot + BACKFILL_LOG_INTERVAL <= current_slot) + .is_none_or(|slot| slot + BACKFILL_LOG_INTERVAL <= current_slot) { last_backfill_log_slot = Some(current_slot); @@ -202,26 +199,31 @@ pub fn spawn_notifier( if display_speed { info!( - log, - "Downloading historical blocks"; - "distance" => distance, - "speed" => sync_speed_pretty(speed), - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(original_oldest_block_slot.saturating_sub(beacon_chain.genesis_backfill_slot))), + distance, + speed = sync_speed_pretty(speed), + est_time = estimated_time_pretty( + speedo.estimated_time_till_slot( + original_oldest_block_slot + .saturating_sub(beacon_chain.genesis_backfill_slot) + ) + ), + "Downloading historical blocks" ); } else { info!( - log, - "Downloading historical blocks"; - "distance" => distance, - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(original_oldest_block_slot.saturating_sub(beacon_chain.genesis_backfill_slot))), + distance, + est_time = estimated_time_pretty( + speedo.estimated_time_till_slot( + original_oldest_block_slot + .saturating_sub(beacon_chain.genesis_backfill_slot) + ) + ), + "Downloading historical blocks" ); } } else if !is_backfilling && last_backfill_log_slot.is_some() { last_backfill_log_slot = None; - info!( - log, - "Historical block download complete"; - ); + info!("Historical block download complete"); } // Log if we are syncing @@ -238,20 +240,20 @@ pub fn spawn_notifier( if display_speed { info!( - log, - "Syncing"; - "peers" => peer_count_pretty(connected_peer_count), - "distance" => distance, - "speed" => sync_speed_pretty(speed), - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + peers = peer_count_pretty(connected_peer_count), + distance, + speed = sync_speed_pretty(speed), + est_time = + estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + "Syncing" ); } else { info!( - log, - "Syncing"; - "peers" => peer_count_pretty(connected_peer_count), - "distance" => distance, - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + peers = peer_count_pretty(connected_peer_count), + distance, + est_time = + estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + "Syncing" ); } } else if current_sync_state.is_synced() { @@ -267,20 +269,18 @@ pub fn spawn_notifier( Ok(ExecutionStatus::Valid(hash)) => format!("{} (verified)", hash), Ok(ExecutionStatus::Optimistic(hash)) => { warn!( - log, - "Head is optimistic"; - "info" => "chain not fully verified, \ - block and attestation production disabled until execution engine syncs", - "execution_block_hash" => ?hash, + info = "chain not fully verified, \ + block and attestation production disabled until execution engine syncs", + execution_block_hash = ?hash, + "Head is optimistic" ); format!("{} (unverified)", hash) } Ok(ExecutionStatus::Invalid(hash)) => { crit!( - log, - "Head execution payload is invalid"; - "msg" => "this scenario may be unrecoverable", - "execution_block_hash" => ?hash, + msg = "this scenario may be unrecoverable", + execution_block_hash = ?hash, + "Head execution payload is invalid" ); format!("{} (invalid)", hash) } @@ -288,35 +288,33 @@ pub fn spawn_notifier( }; info!( - log, - "Synced"; - "peers" => peer_count_pretty(connected_peer_count), - "exec_hash" => block_hash, - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "epoch" => current_epoch, - "block" => block_info, - "slot" => current_slot, + peers = peer_count_pretty(connected_peer_count), + exec_hash = block_hash, + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + epoch = %current_epoch, + block = block_info, + slot = %current_slot, + "Synced" ); } else { metrics::set_gauge(&metrics::IS_SYNCED, 0); info!( - log, - "Searching for peers"; - "peers" => peer_count_pretty(connected_peer_count), - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "head_slot" => head_slot, - "current_slot" => current_slot, + peers = peer_count_pretty(connected_peer_count), + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + %head_slot, + %current_slot, + "Searching for peers" ); } - eth1_logging(&beacon_chain, &log); - bellatrix_readiness_logging(current_slot, &beacon_chain, &log).await; - capella_readiness_logging(current_slot, &beacon_chain, &log).await; - deneb_readiness_logging(current_slot, &beacon_chain, &log).await; - electra_readiness_logging(current_slot, &beacon_chain, &log).await; - fulu_readiness_logging(current_slot, &beacon_chain, &log).await; + eth1_logging(&beacon_chain); + bellatrix_readiness_logging(current_slot, &beacon_chain).await; + capella_readiness_logging(current_slot, &beacon_chain).await; + deneb_readiness_logging(current_slot, &beacon_chain).await; + electra_readiness_logging(current_slot, &beacon_chain).await; + fulu_readiness_logging(current_slot, &beacon_chain).await; } }; @@ -331,7 +329,6 @@ pub fn spawn_notifier( async fn bellatrix_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let merge_completed = beacon_chain .canonical_head @@ -355,10 +352,9 @@ async fn bellatrix_readiness_logging( // Logging of the EE being offline is handled in the other readiness logging functions. if !beacon_chain.is_time_to_prepare_for_capella(current_slot) { error!( - log, - "Execution endpoint required"; - "info" => "you need an execution engine to validate blocks, see: \ - https://lighthouse-book.sigmaprime.io/merge-migration.html" + info = "you need an execution engine to validate blocks, see: \ + https://lighthouse-book.sigmaprime.io/merge-migration.html", + "Execution endpoint required" ); } return; @@ -375,12 +371,11 @@ async fn bellatrix_readiness_logging( terminal_block_hash_epoch: None, } => { info!( - log, - "Ready for Bellatrix"; - "terminal_total_difficulty" => %ttd, - "current_difficulty" => current_difficulty + terminal_total_difficulty = %ttd, + current_difficulty = current_difficulty .map(|d| d.to_string()) .unwrap_or_else(|| "??".into()), + "Ready for Bellatrix" ) } MergeConfig { @@ -389,29 +384,25 @@ async fn bellatrix_readiness_logging( terminal_block_hash_epoch: Some(terminal_block_hash_epoch), } => { info!( - log, - "Ready for Bellatrix"; - "info" => "you are using override parameters, please ensure that you \ - understand these parameters and their implications.", - "terminal_block_hash" => ?terminal_block_hash, - "terminal_block_hash_epoch" => ?terminal_block_hash_epoch, + info = "you are using override parameters, please ensure that you \ + understand these parameters and their implications.", + ?terminal_block_hash, + ?terminal_block_hash_epoch, + "Ready for Bellatrix" ) } other => error!( - log, - "Inconsistent merge configuration"; - "config" => ?other + config = ?other, + "Inconsistent merge configuration" ), }, readiness @ BellatrixReadiness::NotSynced => warn!( - log, - "Not ready Bellatrix"; - "info" => %readiness, + info = %readiness, + "Not ready Bellatrix" ), readiness @ BellatrixReadiness::NoExecutionEndpoint => warn!( - log, - "Not ready for Bellatrix"; - "info" => %readiness, + info = %readiness, + "Not ready for Bellatrix" ), } } @@ -420,7 +411,6 @@ async fn bellatrix_readiness_logging( async fn capella_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let capella_completed = beacon_chain .canonical_head @@ -442,10 +432,9 @@ async fn capella_readiness_logging( // Logging of the EE being offline is handled in the other readiness logging functions. if !beacon_chain.is_time_to_prepare_for_deneb(current_slot) { error!( - log, - "Execution endpoint required"; - "info" => "you need a Capella enabled execution engine to validate blocks, see: \ - https://lighthouse-book.sigmaprime.io/merge-migration.html" + info = "you need a Capella enabled execution engine to validate blocks, see: \ + https://lighthouse-book.sigmaprime.io/merge-migration.html", + "Execution endpoint required" ); } return; @@ -454,24 +443,21 @@ async fn capella_readiness_logging( match beacon_chain.check_capella_readiness().await { CapellaReadiness::Ready => { info!( - log, - "Ready for Capella"; - "info" => "ensure the execution endpoint is updated to the latest Capella/Shanghai release" + info = "ensure the execution endpoint is updated to the latest Capella/Shanghai release", + "Ready for Capella" ) } readiness @ CapellaReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Capella"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Capella" ) } readiness => warn!( - log, - "Not ready for Capella"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Capella" ), } } @@ -480,7 +466,6 @@ async fn capella_readiness_logging( async fn deneb_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let deneb_completed = beacon_chain .canonical_head @@ -500,9 +485,8 @@ async fn deneb_readiness_logging( if deneb_completed && !has_execution_layer { error!( - log, - "Execution endpoint required"; - "info" => "you need a Deneb enabled execution engine to validate blocks." + info = "you need a Deneb enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -510,24 +494,22 @@ async fn deneb_readiness_logging( match beacon_chain.check_deneb_readiness().await { DenebReadiness::Ready => { info!( - log, - "Ready for Deneb"; - "info" => "ensure the execution endpoint is updated to the latest Deneb/Cancun release" + info = + "ensure the execution endpoint is updated to the latest Deneb/Cancun release", + "Ready for Deneb" ) } readiness @ DenebReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Deneb"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Deneb" ) } readiness => warn!( - log, - "Not ready for Deneb"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Deneb" ), } } @@ -535,7 +517,6 @@ async fn deneb_readiness_logging( async fn electra_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let electra_completed = beacon_chain .canonical_head @@ -556,9 +537,8 @@ async fn electra_readiness_logging( if electra_completed && !has_execution_layer { // When adding a new fork, add a check for the next fork readiness here. error!( - log, - "Execution endpoint required"; - "info" => "you need a Electra enabled execution engine to validate blocks." + info = "you need a Electra enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -566,24 +546,22 @@ async fn electra_readiness_logging( match beacon_chain.check_electra_readiness().await { ElectraReadiness::Ready => { info!( - log, - "Ready for Electra"; - "info" => "ensure the execution endpoint is updated to the latest Electra/Prague release" + info = + "ensure the execution endpoint is updated to the latest Electra/Prague release", + "Ready for Electra" ) } readiness @ ElectraReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Electra"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Electra" ) } readiness => warn!( - log, - "Not ready for Electra"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Electra" ), } } @@ -592,7 +570,6 @@ async fn electra_readiness_logging( async fn fulu_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let fulu_completed = beacon_chain .canonical_head @@ -612,9 +589,8 @@ async fn fulu_readiness_logging( if fulu_completed && !has_execution_layer { error!( - log, - "Execution endpoint required"; - "info" => "you need a Fulu enabled execution engine to validate blocks." + info = "you need a Fulu enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -622,102 +598,86 @@ async fn fulu_readiness_logging( match beacon_chain.check_fulu_readiness().await { FuluReadiness::Ready => { info!( - log, - "Ready for Fulu"; - "info" => "ensure the execution endpoint is updated to the latest Fulu release" + info = "ensure the execution endpoint is updated to the latest Fulu release", + "Ready for Fulu" ) } readiness @ FuluReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Fulu"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Fulu" ) } readiness => warn!( - log, - "Not ready for Fulu"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Fulu" ), } } -async fn genesis_execution_payload_logging( - beacon_chain: &BeaconChain, - log: &Logger, -) { +async fn genesis_execution_payload_logging(beacon_chain: &BeaconChain) { match beacon_chain .check_genesis_execution_payload_is_correct() .await { Ok(GenesisExecutionPayloadStatus::Correct(block_hash)) => { info!( - log, - "Execution enabled from genesis"; - "genesis_payload_block_hash" => ?block_hash, + genesis_payload_block_hash = ?block_hash, + "Execution enabled from genesis" ); } Ok(GenesisExecutionPayloadStatus::BlockHashMismatch { got, expected }) => { error!( - log, - "Genesis payload block hash mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_block_hash" => ?expected, - "execution_node_block_hash" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_block_hash = ?expected, + execution_node_block_hash = ?got, + "Genesis payload block hash mismatch" ); } Ok(GenesisExecutionPayloadStatus::TransactionsRootMismatch { got, expected }) => { error!( - log, - "Genesis payload transactions root mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_transactions_root" => ?expected, - "execution_node_transactions_root" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_transactions_root = ?expected, + execution_node_transactions_root = ?got, + "Genesis payload transactions root mismatch" ); } Ok(GenesisExecutionPayloadStatus::WithdrawalsRootMismatch { got, expected }) => { error!( - log, - "Genesis payload withdrawals root mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_withdrawals_root" => ?expected, - "execution_node_withdrawals_root" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_withdrawals_root = ?expected, + execution_node_withdrawals_root = ?got, + "Genesis payload withdrawals root mismatch" ); } Ok(GenesisExecutionPayloadStatus::OtherMismatch) => { error!( - log, - "Genesis payload header mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "detail" => "see debug logs for payload headers" + info = "genesis is misconfigured and likely to fail", + detail = "see debug logs for payload headers", + "Genesis payload header mismatch" ); } Ok(GenesisExecutionPayloadStatus::Irrelevant) => { - info!( - log, - "Execution is not enabled from genesis"; - ); + info!("Execution is not enabled from genesis"); } Ok(GenesisExecutionPayloadStatus::AlreadyHappened) => { warn!( - log, - "Unable to check genesis which has already occurred"; - "info" => "this is probably a race condition or a bug" + info = "this is probably a race condition or a bug", + "Unable to check genesis which has already occurred" ); } Err(e) => { error!( - log, - "Unable to check genesis execution payload"; - "error" => ?e + error = ?e, + "Unable to check genesis execution payload" ); } } } -fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger) { +fn eth1_logging(beacon_chain: &BeaconChain) { let current_slot_opt = beacon_chain.slot().ok(); // Perform some logging about the eth1 chain @@ -733,13 +693,12 @@ fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger &beacon_chain.spec, ) { debug!( - log, - "Eth1 cache sync status"; - "eth1_head_block" => status.head_block_number, - "latest_cached_block_number" => status.latest_cached_block_number, - "latest_cached_timestamp" => status.latest_cached_block_timestamp, - "voting_target_timestamp" => status.voting_target_timestamp, - "ready" => status.lighthouse_is_cached_and_ready + eth1_head_block = status.head_block_number, + latest_cached_block_number = status.latest_cached_block_number, + latest_cached_timestamp = status.latest_cached_block_timestamp, + voting_target_timestamp = status.voting_target_timestamp, + ready = status.lighthouse_is_cached_and_ready, + "Eth1 cache sync status" ); if !status.lighthouse_is_cached_and_ready { @@ -755,16 +714,12 @@ fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger .unwrap_or_else(|| "initializing deposits".to_string()); warn!( - log, - "Syncing deposit contract block cache"; - "est_blocks_remaining" => distance, + est_blocks_remaining = distance, + "Syncing deposit contract block cache" ); } } else { - error!( - log, - "Unable to determine deposit contract sync status"; - ); + error!("Unable to determine deposit contract sync status"); } } } diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index 8ccd50aad8..fa08364251 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -8,7 +8,6 @@ edition = { workspace = true } environment = { workspace = true } eth1_test_rig = { workspace = true } serde_yaml = { workspace = true } -sloggers = { workspace = true } [dependencies] eth2 = { workspace = true } @@ -22,10 +21,10 @@ metrics = { workspace = true } parking_lot = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } superstruct = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } diff --git a/beacon_node/eth1/src/service.rs b/beacon_node/eth1/src/service.rs index 71ab98a6a2..6b10bd2215 100644 --- a/beacon_node/eth1/src/service.rs +++ b/beacon_node/eth1/src/service.rs @@ -13,13 +13,13 @@ use futures::future::TryFutureExt; use parking_lot::{RwLock, RwLockReadGuard}; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info, trace, warn, Logger}; use std::fmt::Debug; use std::ops::{Range, RangeInclusive}; use std::path::PathBuf; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::time::{interval_at, Duration, Instant}; +use tracing::{debug, error, info, trace, warn}; use types::{ChainSpec, DepositTreeSnapshot, Eth1Data, EthSpec, Unsigned}; /// Indicates the default eth1 chain id we use for the deposit contract. @@ -58,22 +58,16 @@ type EndpointState = Result<(), EndpointError>; /// Returns `Ok` if the endpoint is usable, i.e. is reachable and has a correct network id and /// chain id. Otherwise it returns `Err`. -async fn endpoint_state( - endpoint: &HttpJsonRpc, - config_chain_id: &Eth1Id, - log: &Logger, -) -> EndpointState { +async fn endpoint_state(endpoint: &HttpJsonRpc, config_chain_id: &Eth1Id) -> EndpointState { let error_connecting = |e: String| { debug!( - log, - "eth1 endpoint error"; - "endpoint" => %endpoint, - "error" => &e, + %endpoint, + error = &e, + "eth1 endpoint error" ); warn!( - log, - "Error connecting to eth1 node endpoint"; - "endpoint" => %endpoint, + %endpoint, + "Error connecting to eth1 node endpoint" ); EndpointError::RequestFailed(e) }; @@ -86,19 +80,17 @@ async fn endpoint_state( // Handle the special case if chain_id == Eth1Id::Custom(0) { warn!( - log, - "Remote execution node is not synced"; - "endpoint" => %endpoint, + %endpoint, + "Remote execution node is not synced" ); return Err(EndpointError::FarBehind); } if &chain_id != config_chain_id { warn!( - log, - "Invalid execution chain ID. Please switch to correct chain ID on endpoint"; - "endpoint" => %endpoint, - "expected" => ?config_chain_id, - "received" => ?chain_id, + %endpoint, + expected = ?config_chain_id, + received = ?chain_id, + "Invalid execution chain ID. Please switch to correct chain ID on endpoint" ); Err(EndpointError::WrongChainId) } else { @@ -134,10 +126,9 @@ async fn get_remote_head_and_new_block_ranges( .unwrap_or(u64::MAX); if remote_head_block.timestamp + node_far_behind_seconds < now { warn!( - service.log, - "Execution endpoint is not synced"; - "endpoint" => %endpoint, - "last_seen_block_unix_timestamp" => remote_head_block.timestamp, + %endpoint, + last_seen_block_unix_timestamp = remote_head_block.timestamp, + "Execution endpoint is not synced" ); return Err(Error::EndpointError(EndpointError::FarBehind)); } @@ -145,9 +136,8 @@ async fn get_remote_head_and_new_block_ranges( let handle_remote_not_synced = |e| { if let Error::RemoteNotSynced { .. } = e { warn!( - service.log, - "Execution endpoint is not synced"; - "endpoint" => %endpoint, + %endpoint, + "Execution endpoint is not synced" ); } e @@ -392,12 +382,11 @@ pub fn endpoint_from_config(config: &Config) -> Result { #[derive(Clone)] pub struct Service { inner: Arc, - pub log: Logger, } impl Service { /// Creates a new service. Does not attempt to connect to the eth1 node. - pub fn new(config: Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Config, spec: Arc) -> Result { Ok(Self { inner: Arc::new(Inner { block_cache: <_>::default(), @@ -410,7 +399,6 @@ impl Service { config: RwLock::new(config), spec, }), - log, }) } @@ -425,7 +413,6 @@ impl Service { /// Creates a new service, initializing the deposit tree from a snapshot. pub fn from_deposit_snapshot( config: Config, - log: Logger, spec: Arc, deposit_snapshot: &DepositTreeSnapshot, ) -> Result { @@ -444,7 +431,6 @@ impl Service { config: RwLock::new(config), spec, }), - log, }) } @@ -464,16 +450,10 @@ impl Service { } /// Recover the deposit and block caches from encoded bytes. - pub fn from_bytes( - bytes: &[u8], - config: Config, - log: Logger, - spec: Arc, - ) -> Result { + pub fn from_bytes(bytes: &[u8], config: Config, spec: Arc) -> Result { let inner = Inner::from_bytes(bytes, config, spec)?; Ok(Self { inner: Arc::new(inner), - log, }) } @@ -621,11 +601,10 @@ impl Service { &self, ) -> Result<(DepositCacheUpdateOutcome, BlockCacheUpdateOutcome), String> { let client = self.client(); - let log = self.log.clone(); let chain_id = self.config().chain_id.clone(); let node_far_behind_seconds = self.inner.config.read().node_far_behind_seconds; - match endpoint_state(client, &chain_id, &log).await { + match endpoint_state(client, &chain_id).await { Ok(()) => crate::metrics::set_gauge(&metrics::ETH1_CONNECTED, 1), Err(e) => { crate::metrics::set_gauge(&metrics::ETH1_CONNECTED, 0); @@ -655,10 +634,9 @@ impl Service { { let mut deposit_cache = self.inner.deposit_cache.write(); debug!( - self.log, - "Resetting last processed block"; - "old_block_number" => deposit_cache.last_processed_block, - "new_block_number" => deposit_cache.cache.latest_block_number(), + old_block_number = deposit_cache.last_processed_block, + new_block_number = deposit_cache.cache.latest_block_number(), + "Resetting last processed block" ); deposit_cache.last_processed_block = Some(deposit_cache.cache.latest_block_number()); @@ -668,11 +646,11 @@ impl Service { outcome_result.map_err(|e| format!("Failed to update deposit cache: {:?}", e))?; trace!( - self.log, - "Updated deposit cache"; - "cached_deposits" => self.inner.deposit_cache.read().cache.len(), - "logs_imported" => outcome.logs_imported, - "last_processed_execution_block" => self.inner.deposit_cache.read().last_processed_block, + cached_deposits = self.inner.deposit_cache.read().cache.len(), + logs_imported = outcome.logs_imported, + last_processed_execution_block = + self.inner.deposit_cache.read().last_processed_block, + "Updated deposit cache" ); Ok::<_, String>(outcome) }; @@ -684,11 +662,10 @@ impl Service { .map_err(|e| format!("Failed to update deposit contract block cache: {:?}", e))?; trace!( - self.log, - "Updated deposit contract block cache"; - "cached_blocks" => self.inner.block_cache.read().len(), - "blocks_imported" => outcome.blocks_imported, - "head_block" => outcome.head_block_number, + cached_blocks = self.inner.block_cache.read().len(), + blocks_imported = outcome.blocks_imported, + head_block = outcome.head_block_number, + "Updated deposit contract block cache" ); Ok::<_, String>(outcome) }; @@ -727,17 +704,15 @@ impl Service { let update_result = self.update().await; match update_result { Err(e) => error!( - self.log, - "Error updating deposit contract cache"; - "retry_millis" => update_interval.as_millis(), - "error" => e, + retry_millis = update_interval.as_millis(), + error = e, + "Error updating deposit contract cache" ), Ok((deposit, block)) => debug!( - self.log, - "Updated deposit contract cache"; - "retry_millis" => update_interval.as_millis(), - "blocks" => format!("{:?}", block), - "deposits" => format!("{:?}", deposit), + retry_millis = update_interval.as_millis(), + ?block, + ?deposit, + "Updated deposit contract cache" ), }; let optional_eth1data = self.inner.to_finalize.write().take(); @@ -752,23 +727,20 @@ impl Service { if deposit_count_to_finalize > already_finalized { match self.finalize_deposits(eth1data_to_finalize) { Err(e) => warn!( - self.log, - "Failed to finalize deposit cache"; - "error" => ?e, - "info" => "this should resolve on its own" + error = ?e, + info = "this should resolve on its own", + "Failed to finalize deposit cache" ), Ok(()) => info!( - self.log, - "Successfully finalized deposit tree"; - "finalized deposit count" => deposit_count_to_finalize, + finalized_deposit_count = deposit_count_to_finalize, + "Successfully finalized deposit tree" ), } } else { debug!( - self.log, - "Deposits tree already finalized"; - "already_finalized" => already_finalized, - "deposit_count_to_finalize" => deposit_count_to_finalize, + %already_finalized, + %deposit_count_to_finalize, + "Deposits tree already finalized" ); } } @@ -889,10 +861,7 @@ impl Service { let deposit_contract_address_ref: &str = &deposit_contract_address; for block_range in block_number_chunks.into_iter() { if block_range.is_empty() { - debug!( - self.log, - "No new blocks to scan for logs"; - ); + debug!("No new blocks to scan for logs"); continue; } @@ -946,11 +915,7 @@ impl Service { Ok::<_, Error>(()) })?; - debug!( - self.log, - "Imported deposit logs chunk"; - "logs" => logs.len(), - ); + debug!(logs = logs.len(), "Imported deposit logs chunk"); cache.last_processed_block = Some(block_range.end.saturating_sub(1)); @@ -963,18 +928,16 @@ impl Service { if logs_imported > 0 { info!( - self.log, - "Imported deposit log(s)"; - "latest_block" => self.inner.deposit_cache.read().cache.latest_block_number(), - "total" => self.deposit_cache_len(), - "new" => logs_imported + latest_block = self.inner.deposit_cache.read().cache.latest_block_number(), + total = self.deposit_cache_len(), + new = logs_imported, + "Imported deposit log(s)" ); } else { debug!( - self.log, - "No new deposits found"; - "latest_block" => self.inner.deposit_cache.read().cache.latest_block_number(), - "total_deposits" => self.deposit_cache_len(), + latest_block = self.inner.deposit_cache.read().cache.latest_block_number(), + total_deposits = self.deposit_cache_len(), + "No new deposits found" ); } @@ -1058,10 +1021,9 @@ impl Service { .collect::>(); debug!( - self.log, - "Downloading execution blocks"; - "first" => ?required_block_numbers.first(), - "last" => ?required_block_numbers.last(), + first = ?required_block_numbers.first(), + last = ?required_block_numbers.last(), + "Downloading execution blocks" ); // Produce a stream from the list of required block numbers and return a future that @@ -1116,19 +1078,17 @@ impl Service { if blocks_imported > 0 { debug!( - self.log, - "Imported execution block(s)"; - "latest_block_age" => latest_block_mins, - "latest_block" => block_cache.highest_block_number(), - "total_cached_blocks" => block_cache.len(), - "new" => %blocks_imported + latest_block_age = latest_block_mins, + latest_block = block_cache.highest_block_number(), + total_cached_blocks = block_cache.len(), + new = %blocks_imported, + "Imported execution block(s)" ); } else { debug!( - self.log, - "No new execution blocks imported"; - "latest_block" => block_cache.highest_block_number(), - "cached_blocks" => block_cache.len(), + latest_block = block_cache.highest_block_number(), + cached_blocks = block_cache.len(), + "No new execution blocks imported" ); } diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index e442ce4863..48ed189259 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -4,7 +4,7 @@ use eth1::{Config, Eth1Endpoint, Service}; use eth1::{DepositCache, DEFAULT_CHAIN_ID}; use eth1_test_rig::{AnvilEth1Instance, Http, Middleware, Provider}; use execution_layer::http::{deposit_methods::*, HttpJsonRpc, Log}; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use merkle_proof::verify_merkle_proof; use sensitive_url::SensitiveUrl; use std::ops::Range; @@ -19,11 +19,10 @@ use types::{ const DEPOSIT_CONTRACT_TREE_DEPTH: usize = 32; pub fn new_env() -> Environment { + create_test_tracing_subscriber(); EnvironmentBuilder::minimal() .multi_threaded_tokio_runtime() .expect("should start tokio runtime") - .test_logger() - .expect("should start null logger") .build() .expect("should build env") } @@ -100,9 +99,8 @@ mod eth1_cache { #[tokio::test] async fn simple_scenario() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - for follow_distance in 0..3 { let eth1 = new_anvil_instance() .await @@ -123,12 +121,8 @@ mod eth1_cache { }; let cache_follow_distance = config.cache_follow_distance(); - let service = Service::new( - config, - log.clone(), - Arc::new(MainnetEthSpec::default_spec()), - ) - .unwrap(); + let service = + Service::new(config, Arc::new(MainnetEthSpec::default_spec())).unwrap(); // Create some blocks and then consume them, performing the test `rounds` times. for round in 0..2 { @@ -186,9 +180,8 @@ mod eth1_cache { #[tokio::test] async fn big_skip() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -208,7 +201,6 @@ mod eth1_cache { block_cache_truncation: Some(cache_len), ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -241,9 +233,8 @@ mod eth1_cache { /// cache size. #[tokio::test] async fn pruning() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -263,7 +254,6 @@ mod eth1_cache { block_cache_truncation: Some(cache_len), ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -293,9 +283,8 @@ mod eth1_cache { #[tokio::test] async fn double_update() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 16; let eth1 = new_anvil_instance() @@ -314,7 +303,6 @@ mod eth1_cache { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -346,9 +334,8 @@ mod deposit_tree { #[tokio::test] async fn updating() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 4; let eth1 = new_anvil_instance() @@ -369,7 +356,6 @@ mod deposit_tree { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -427,9 +413,8 @@ mod deposit_tree { #[tokio::test] async fn double_update() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 8; let eth1 = new_anvil_instance() @@ -451,7 +436,6 @@ mod deposit_tree { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -689,9 +673,8 @@ mod fast { // with the deposit count and root computed from the deposit cache. #[tokio::test] async fn deposit_cache_query() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -712,7 +695,6 @@ mod fast { block_cache_truncation: None, ..Config::default() }, - log, spec.clone(), ) .unwrap(); @@ -772,9 +754,8 @@ mod persist { use super::*; #[tokio::test] async fn test_persist_caches() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -793,12 +774,8 @@ mod persist { block_cache_truncation: None, ..Config::default() }; - let service = Service::new( - config.clone(), - log.clone(), - Arc::new(MainnetEthSpec::default_spec()), - ) - .unwrap(); + let service = + Service::new(config.clone(), Arc::new(MainnetEthSpec::default_spec())).unwrap(); let n = 10; let deposits: Vec<_> = (0..n).map(|_| random_deposit_data()).collect(); for deposit in &deposits { @@ -840,7 +817,6 @@ mod persist { let recovered_service = Service::from_bytes( ð1_bytes, config, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 7eb7b4a15e..f56159c7b5 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -12,7 +12,6 @@ arc-swap = "1.6.0" builder_client = { path = "../builder_client" } bytes = { workspace = true } eth2 = { workspace = true } -eth2_network_config = { workspace = true } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } ethers-core = { workspace = true } @@ -36,7 +35,6 @@ sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } ssz_types = { workspace = true } state_processing = { workspace = true } @@ -46,6 +44,7 @@ task_executor = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } triehash = "0.8.4" diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index b9d878b1f8..aed6cdba67 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -142,11 +142,18 @@ pub struct ExecutionBlock { pub block_number: u64, pub parent_hash: ExecutionBlockHash, - pub total_difficulty: Uint256, + pub total_difficulty: Option, #[serde(with = "serde_utils::u64_hex_be")] pub timestamp: u64, } +impl ExecutionBlock { + pub fn terminal_total_difficulty_reached(&self, terminal_total_difficulty: Uint256) -> bool { + self.total_difficulty + .is_none_or(|td| td >= terminal_total_difficulty) + } +} + #[superstruct( variants(V1, V2, V3), variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),), diff --git a/beacon_node/execution_layer/src/engines.rs b/beacon_node/execution_layer/src/engines.rs index 75d0b872ce..b9e030703d 100644 --- a/beacon_node/execution_layer/src/engines.rs +++ b/beacon_node/execution_layer/src/engines.rs @@ -6,7 +6,6 @@ use crate::engine_api::{ }; use crate::{ClientVersionV1, HttpJsonRpc}; use lru::LruCache; -use slog::{debug, error, info, warn, Logger}; use std::future::Future; use std::num::NonZeroUsize; use std::sync::Arc; @@ -14,6 +13,7 @@ use std::time::Duration; use task_executor::TaskExecutor; use tokio::sync::{watch, Mutex, RwLock}; use tokio_stream::wrappers::WatchStream; +use tracing::{debug, error, info, warn}; use types::non_zero_usize::new_non_zero_usize; use types::ExecutionBlockHash; @@ -128,19 +128,17 @@ pub struct Engine { state: RwLock, latest_forkchoice_state: RwLock>, executor: TaskExecutor, - log: Logger, } impl Engine { /// Creates a new, offline engine. - pub fn new(api: HttpJsonRpc, executor: TaskExecutor, log: &Logger) -> Self { + pub fn new(api: HttpJsonRpc, executor: TaskExecutor) -> Self { Self { api, payload_id_cache: Mutex::new(LruCache::new(PAYLOAD_ID_LRU_CACHE_SIZE)), state: Default::default(), latest_forkchoice_state: Default::default(), executor, - log: log.clone(), } } @@ -167,7 +165,6 @@ impl Engine { &self, forkchoice_state: ForkchoiceState, payload_attributes: Option, - log: &Logger, ) -> Result { let response = self .api @@ -180,11 +177,7 @@ impl Engine { { self.payload_id_cache.lock().await.put(key, payload_id); } else { - debug!( - log, - "Engine returned unexpected payload_id"; - "payload_id" => ?payload_id - ); + debug!(?payload_id, "Engine returned unexpected payload_id"); } } @@ -205,33 +198,24 @@ impl Engine { if let Some(forkchoice_state) = latest_forkchoice_state { if forkchoice_state.head_block_hash == ExecutionBlockHash::zero() { debug!( - self.log, - "No need to call forkchoiceUpdated"; - "msg" => "head does not have execution enabled", + msg = "head does not have execution enabled", + "No need to call forkchoiceUpdated" ); return; } - info!( - self.log, - "Issuing forkchoiceUpdated"; - "forkchoice_state" => ?forkchoice_state, - ); + info!(?forkchoice_state, "Issuing forkchoiceUpdated"); // For simplicity, payload attributes are never included in this call. It may be // reasonable to include them in the future. if let Err(e) = self.api.forkchoice_updated(forkchoice_state, None).await { debug!( - self.log, - "Failed to issue latest head to engine"; - "error" => ?e, + error = ?e, + "Failed to issue latest head to engine" ); } } else { - debug!( - self.log, - "No head, not sending to engine"; - ); + debug!("No head, not sending to engine"); } } @@ -252,18 +236,12 @@ impl Engine { Ok(()) => { let mut state = self.state.write().await; if **state != EngineStateInternal::Synced { - info!( - self.log, - "Execution engine online"; - ); + info!("Execution engine online"); // Send the node our latest forkchoice_state. self.send_latest_forkchoice_state().await; } else { - debug!( - self.log, - "Execution engine online"; - ); + debug!("Execution engine online"); } state.update(EngineStateInternal::Synced); (**state, ResponseCacheAction::Update) @@ -275,9 +253,8 @@ impl Engine { } Err(EngineApiError::Auth(err)) => { error!( - self.log, - "Failed jwt authorization"; - "error" => ?err, + error = ?err, + "Failed jwt authorization" ); let mut state = self.state.write().await; @@ -286,9 +263,8 @@ impl Engine { } Err(e) => { error!( - self.log, - "Error during execution engine upcheck"; - "error" => ?e, + error = ?e, + "Error during execution engine upcheck" ); let mut state = self.state.write().await; @@ -308,9 +284,9 @@ impl Engine { .get_engine_capabilities(Some(CACHED_RESPONSE_AGE_LIMIT)) .await { - warn!(self.log, - "Error during exchange capabilities"; - "error" => ?e, + warn!( + error = ?e, + "Error during exchange capabilities" ) } else { // no point in running this if there was an error fetching the capabilities @@ -326,11 +302,7 @@ impl Engine { } } - debug!( - self.log, - "Execution engine upcheck complete"; - "state" => ?state, - ); + debug!(?state, "Execution engine upcheck complete"); } /// Returns the execution engine capabilities resulting from a call to @@ -395,11 +367,7 @@ impl Engine { Ok(result) } Err(error) => { - warn!( - self.log, - "Execution engine call failed"; - "error" => ?error, - ); + warn!(?error, "Execution engine call failed"); // The node just returned an error, run an upcheck so we can update the endpoint // state. diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 6e5e4fca01..6644e46a0d 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -21,12 +21,12 @@ use eth2::types::{builder_bid::SignedBuilderBid, BlobsBundle, ForkVersionedRespo use ethers_core::types::Transaction as EthersTransaction; use fixed_bytes::UintExtended; use fork_choice::ForkchoiceUpdateParameters; +use logging::crit; use lru::LruCache; use payload_status::process_payload_status; pub use payload_status::PayloadStatus; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::collections::{hash_map::Entry, HashMap}; use std::fmt; @@ -43,6 +43,7 @@ use tokio::{ time::sleep, }; use tokio_stream::wrappers::WatchStream; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::beacon_block_body::KzgCommitments; use types::builder_bid::BuilderBid; @@ -422,7 +423,6 @@ struct Inner { proposers: RwLock>, executor: TaskExecutor, payload_cache: PayloadCache, - log: Logger, /// Track whether the last `newPayload` call errored. /// /// This is used *only* in the informational sync status endpoint, so that a VC using this @@ -441,6 +441,8 @@ pub struct Config { pub builder_header_timeout: Option, /// User agent to send with requests to the builder API. pub builder_user_agent: Option, + /// Disable ssz requests on builder. Only use json. + pub disable_builder_ssz_requests: bool, /// JWT secret for the above endpoint running the engine api. pub secret_file: Option, /// The default fee recipient to use on the beacon node if none if provided from @@ -464,12 +466,13 @@ pub struct ExecutionLayer { impl ExecutionLayer { /// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP. - pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result { + pub fn from_config(config: Config, executor: TaskExecutor) -> Result { let Config { execution_endpoint: url, builder_url, builder_user_agent, builder_header_timeout, + disable_builder_ssz_requests, secret_file, suggested_fee_recipient, jwt_id, @@ -497,7 +500,7 @@ impl ExecutionLayer { .map_err(Error::InvalidJWTSecret) } else { // Create a new file and write a randomly generated secret to it if file does not exist - warn!(log, "No JWT found on disk. Generating"; "path" => %secret_file.display()); + warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); std::fs::File::options() .write(true) .create_new(true) @@ -514,10 +517,10 @@ impl ExecutionLayer { let engine: Engine = { let auth = Auth::new(jwt_key, jwt_id, jwt_version); - debug!(log, "Loaded execution endpoint"; "endpoint" => %execution_url, "jwt_path" => ?secret_file.as_path()); + debug!(endpoint = %execution_url, jwt_path = ?secret_file.as_path(),"Loaded execution endpoint"); let api = HttpJsonRpc::new_with_auth(execution_url, auth, execution_timeout_multiplier) .map_err(Error::ApiError)?; - Engine::new(api, executor.clone(), &log) + Engine::new(api, executor.clone()) }; let inner = Inner { @@ -530,7 +533,6 @@ impl ExecutionLayer { execution_blocks: Mutex::new(LruCache::new(EXECUTION_BLOCKS_LRU_CACHE_SIZE)), executor, payload_cache: PayloadCache::default(), - log, last_new_payload_errored: RwLock::new(false), }; @@ -539,7 +541,12 @@ impl ExecutionLayer { }; if let Some(builder_url) = builder_url { - el.set_builder_url(builder_url, builder_user_agent, builder_header_timeout)?; + el.set_builder_url( + builder_url, + builder_user_agent, + builder_header_timeout, + disable_builder_ssz_requests, + )?; } Ok(el) @@ -562,18 +569,20 @@ impl ExecutionLayer { builder_url: SensitiveUrl, builder_user_agent: Option, builder_header_timeout: Option, + disable_ssz: bool, ) -> Result<(), Error> { let builder_client = BuilderHttpClient::new( builder_url.clone(), builder_user_agent, builder_header_timeout, + disable_ssz, ) .map_err(Error::Builder)?; info!( - self.log(), - "Using external block builder"; - "builder_url" => ?builder_url, - "local_user_agent" => builder_client.get_user_agent(), + ?builder_url, + local_user_agent = builder_client.get_user_agent(), + ssz_disabled = disable_ssz, + "Using external block builder" ); self.inner.builder.swap(Some(Arc::new(builder_client))); Ok(()) @@ -610,7 +619,7 @@ impl ExecutionLayer { } /// Get the current difficulty of the PoW chain. - pub async fn get_current_difficulty(&self) -> Result { + pub async fn get_current_difficulty(&self) -> Result, ApiError> { let block = self .engine() .api @@ -644,10 +653,6 @@ impl ExecutionLayer { &self.inner.proposers } - fn log(&self) -> &Logger { - &self.inner.log - } - pub async fn execution_engine_forkchoice_lock(&self) -> MutexGuard<'_, ()> { self.inner.execution_engine_forkchoice_lock.lock().await } @@ -705,16 +710,15 @@ impl ExecutionLayer { .await .map_err(|e| { error!( - el.log(), - "Failed to clean proposer preparation cache"; - "error" => format!("{:?}", e) + error = ?e, + "Failed to clean proposer preparation cache" ) }) .unwrap_or(()), - None => error!(el.log(), "Failed to get current epoch from slot clock"), + None => error!("Failed to get current epoch from slot clock"), } } else { - error!(el.log(), "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot and retry. sleep(slot_clock.slot_duration()).await; } @@ -854,12 +858,11 @@ impl ExecutionLayer { } else { // If there is no user-provided fee recipient, use a junk value and complain loudly. crit!( - self.log(), - "Fee recipient unknown"; - "msg" => "the suggested_fee_recipient was unknown during block production. \ + msg = "the suggested_fee_recipient was unknown during block production. \ a junk address was used, rewards were lost! \ check the --suggested-fee-recipient flag and VC configuration.", - "proposer_index" => ?proposer_index + ?proposer_index, + "Fee recipient unknown" ); Address::from_slice(&DEFAULT_SUGGESTED_FEE_RECIPIENT) @@ -976,11 +979,10 @@ impl ExecutionLayer { let parent_hash = payload_parameters.parent_hash; info!( - self.log(), - "Requesting blinded header from connected builder"; - "slot" => ?slot, - "pubkey" => ?pubkey, - "parent_hash" => ?parent_hash, + ?slot, + ?pubkey, + ?parent_hash, + "Requesting blinded header from connected builder" ); // Wait for the builder *and* local EL to produce a payload (or return an error). @@ -1001,20 +1003,19 @@ impl ExecutionLayer { ); info!( - self.log(), - "Requested blinded execution payload"; - "relay_fee_recipient" => match &relay_result { + relay_fee_recipient = match &relay_result { Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()), Ok(None) => "empty response".to_string(), Err(_) => "request failed".to_string(), }, - "relay_response_ms" => relay_duration.as_millis(), - "local_fee_recipient" => match &local_result { + relay_response_ms = relay_duration.as_millis(), + local_fee_recipient = match &local_result { Ok(get_payload_response) => format!("{:?}", get_payload_response.fee_recipient()), - Err(_) => "request failed".to_string() + Err(_) => "request failed".to_string(), }, - "local_response_ms" => local_duration.as_millis(), - "parent_hash" => ?parent_hash, + local_response_ms = local_duration.as_millis(), + ?parent_hash, + "Requested blinded execution payload" ); (relay_result, local_result) @@ -1041,24 +1042,21 @@ impl ExecutionLayer { // chain is unhealthy, gotta use local payload match builder_params.chain_health { ChainHealth::Unhealthy(condition) => info!( - self.log(), - "Chain is unhealthy, using local payload"; - "info" => "this helps protect the network. the --builder-fallback flags \ - can adjust the expected health conditions.", - "failed_condition" => ?condition + info = "this helps protect the network. the --builder-fallback flags \ + can adjust the expected health conditions.", + failed_condition = ?condition, + "Chain is unhealthy, using local payload" ), // Intentional no-op, so we never attempt builder API proposals pre-merge. ChainHealth::PreMerge => (), ChainHealth::Optimistic => info!( - self.log(), - "Chain is optimistic; can't build payload"; - "info" => "the local execution engine is syncing and the builder network \ - cannot safely be used - unable to propose block" - ), - ChainHealth::Healthy => crit!( - self.log(), - "got healthy but also not healthy.. this shouldn't happen!" + info = "the local execution engine is syncing and the builder network \ + cannot safely be used - unable to propose block", + "Chain is optimistic; can't build payload" ), + ChainHealth::Healthy => { + crit!("got healthy but also not healthy.. this shouldn't happen!") + } } return self .get_full_payload_caching(payload_parameters) @@ -1075,12 +1073,11 @@ impl ExecutionLayer { match (relay_result, local_result) { (Err(e), Ok(local)) => { warn!( - self.log(), - "Builder error when requesting payload"; - "info" => "falling back to local execution client", - "relay_error" => ?e, - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + info = "falling back to local execution client", + relay_error = ?e, + local_block_hash = ?local.block_hash(), + ?parent_hash, + "Builder error when requesting payload" ); Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1088,11 +1085,10 @@ impl ExecutionLayer { } (Ok(None), Ok(local)) => { info!( - self.log(), - "Builder did not return a payload"; - "info" => "falling back to local execution client", - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + info = "falling back to local execution client", + local_block_hash=?local.block_hash(), + ?parent_hash, + "Builder did not return a payload" ); Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1100,24 +1096,22 @@ impl ExecutionLayer { } (Err(relay_error), Err(local_error)) => { crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL and builder both failed - unable to propose block", - "relay_error" => ?relay_error, - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + info = "the local EL and builder both failed - unable to propose block", + ?relay_error, + ?local_error, + ?parent_hash, + "Unable to produce execution payload" ); Err(Error::CannotProduceHeader) } (Ok(None), Err(local_error)) => { crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL failed and the builder returned nothing - \ - the block proposal will be missed", - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + info = "the local EL failed and the builder returned nothing - \ + the block proposal will be missed", + ?local_error, + ?parent_hash, + "Unable to produce execution payload" ); Err(Error::CannotProduceHeader) @@ -1126,11 +1120,10 @@ impl ExecutionLayer { let header = &relay.data.message.header(); info!( - self.log(), - "Received local and builder payloads"; - "relay_block_hash" => ?header.block_hash(), - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + relay_block_hash = ?header.block_hash(), + local_block_hash=?local.block_hash(), + ?parent_hash, + "Received local and builder payloads" ); // check relay payload validity @@ -1143,12 +1136,11 @@ impl ExecutionLayer { &[reason.as_ref().as_ref()], ); warn!( - self.log(), - "Builder returned invalid payload"; - "info" => "using local payload", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, + info = "using local payload", + %reason, + relay_block_hash = ?header.block_hash(), + ?parent_hash, + "Builder returned invalid payload" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1167,12 +1159,11 @@ impl ExecutionLayer { if local_value >= boosted_relay_value { info!( - self.log(), - "Local block is more profitable than relay block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value, - "boosted_relay_value" => %boosted_relay_value, - "builder_boost_factor" => ?builder_boost_factor, + %local_value, + %relay_value, + %boosted_relay_value, + ?builder_boost_factor, + "Local block is more profitable than relay block" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1181,10 +1172,9 @@ impl ExecutionLayer { if local.should_override_builder().unwrap_or(false) { info!( - self.log(), - "Using local payload because execution engine suggested we ignore builder payload"; - "local_block_value" => %local_value, - "relay_value" => %relay_value + %local_value, + %relay_value, + "Using local payload because execution engine suggested we ignore builder payload" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1192,12 +1182,11 @@ impl ExecutionLayer { } info!( - self.log(), - "Relay block is more profitable than local block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value, - "boosted_relay_value" => %boosted_relay_value, - "builder_boost_factor" => ?builder_boost_factor + %local_value, + %relay_value, + %boosted_relay_value, + ?builder_boost_factor, + "Relay block is more profitable than local block" ); Ok(ProvenancedPayload::try_from(relay.data.message)?) @@ -1206,11 +1195,10 @@ impl ExecutionLayer { let header = &relay.data.message.header(); info!( - self.log(), - "Received builder payload with local error"; - "relay_block_hash" => ?header.block_hash(), - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + relay_block_hash = ?header.block_hash(), + ?local_error, + ?parent_hash, + "Received builder payload with local error" ); match verify_builder_bid(&relay, payload_parameters, None, spec) { @@ -1221,12 +1209,11 @@ impl ExecutionLayer { &[reason.as_ref().as_ref()], ); crit!( - self.log(), - "Builder returned invalid payload"; - "info" => "no local payload either - unable to propose block", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, + info = "no local payload either - unable to propose block", + %reason, + relay_block_hash = ?header.block_hash(), + ?parent_hash, + "Builder returned invalid payload" ); Err(Error::CannotProduceHeader) } @@ -1293,7 +1280,6 @@ impl ExecutionLayer { .notify_forkchoice_updated( fork_choice_state, Some(payload_attributes.clone()), - self.log(), ) .await?; @@ -1301,12 +1287,11 @@ impl ExecutionLayer { Some(payload_id) => payload_id, None => { error!( - self.log(), - "Exec engine unable to produce payload"; - "msg" => "No payload ID, the engine is likely syncing. \ - This has the potential to cause a missed block proposal.", - "status" => ?response.payload_status - ); + msg = "No payload ID, the engine is likely syncing. \ + This has the potential to cause a missed block proposal.", + status = ?response.payload_status, + "Exec engine unable to produce payload" + ); return Err(ApiError::PayloadIdUnavailable); } } @@ -1314,36 +1299,44 @@ impl ExecutionLayer { let payload_response = async { debug!( - self.log(), - "Issuing engine_getPayload"; - "suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(), - "prev_randao" => ?payload_attributes.prev_randao(), - "timestamp" => payload_attributes.timestamp(), - "parent_hash" => ?parent_hash, + suggested_fee_recipient = ?payload_attributes.suggested_fee_recipient(), + prev_randao = ?payload_attributes.prev_randao(), + timestamp = payload_attributes.timestamp(), + ?parent_hash, + "Issuing engine_getPayload" ); let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metrics::GET_PAYLOAD], ); engine.api.get_payload::(current_fork, payload_id).await - }.await?; + } + .await?; - if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.suggested_fee_recipient() { + if payload_response.execution_payload_ref().fee_recipient() + != payload_attributes.suggested_fee_recipient() + { error!( - self.log(), - "Inconsistent fee recipient"; - "msg" => "The fee recipient returned from the Execution Engine differs \ + msg = "The fee recipient returned from the Execution Engine differs \ from the suggested_fee_recipient set on the beacon node. This could \ indicate that fees are being diverted to another address. Please \ ensure that the value of suggested_fee_recipient is set correctly and \ that the Execution Engine is trusted.", - "fee_recipient" => ?payload_response.execution_payload_ref().fee_recipient(), - "suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(), + fee_recipient = ?payload_response.execution_payload_ref().fee_recipient(), + suggested_fee_recipient = ?payload_attributes.suggested_fee_recipient(), + "Inconsistent fee recipient" ); } - if cache_fn(self, (payload_response.execution_payload_ref(), payload_response.blobs_bundle().ok())).is_some() { + if cache_fn( + self, + ( + payload_response.execution_payload_ref(), + payload_response.blobs_bundle().ok(), + ), + ) + .is_some() + { warn!( - self.log(), "Duplicate payload cached, this might indicate redundant proposal \ attempts." ); @@ -1383,18 +1376,17 @@ impl ExecutionLayer { &["new_payload", status_str], ); debug!( - self.log(), - "Processed engine_newPayload"; - "status" => status_str, - "parent_hash" => ?parent_hash, - "block_hash" => ?block_hash, - "block_number" => block_number, - "response_time_ms" => timer.elapsed().as_millis() + status = status_str, + ?parent_hash, + ?block_hash, + block_number, + response_time_ms = timer.elapsed().as_millis(), + "Processed engine_newPayload" ); } *self.inner.last_new_payload_errored.write().await = result.is_err(); - process_payload_status(block_hash, result, self.log()) + process_payload_status(block_hash, result) .map_err(Box::new) .map_err(Error::EngineError) } @@ -1451,12 +1443,11 @@ impl ExecutionLayer { let proposer = self.proposers().read().await.get(&proposers_key).cloned()?; debug!( - self.log(), - "Beacon proposer found"; - "payload_attributes" => ?proposer.payload_attributes, - "head_block_root" => ?head_block_root, - "slot" => current_slot, - "validator_index" => proposer.validator_index, + payload_attributes = ?proposer.payload_attributes, + ?head_block_root, + slot = %current_slot, + validator_index = proposer.validator_index, + "Beacon proposer found" ); Some(proposer.payload_attributes) @@ -1477,13 +1468,12 @@ impl ExecutionLayer { ); debug!( - self.log(), - "Issuing engine_forkchoiceUpdated"; - "finalized_block_hash" => ?finalized_block_hash, - "justified_block_hash" => ?justified_block_hash, - "head_block_hash" => ?head_block_hash, - "head_block_root" => ?head_block_root, - "current_slot" => current_slot, + ?finalized_block_hash, + ?justified_block_hash, + ?head_block_hash, + ?head_block_root, + ?current_slot, + "Issuing engine_forkchoiceUpdated" ); let next_slot = current_slot + 1; @@ -1499,12 +1489,7 @@ impl ExecutionLayer { lookahead, ); } else { - debug!( - self.log(), - "Late payload attributes"; - "timestamp" => ?timestamp, - "now" => ?now, - ) + debug!(?timestamp, ?now, "Late payload attributes") } } } @@ -1523,7 +1508,7 @@ impl ExecutionLayer { .engine() .request(|engine| async move { engine - .notify_forkchoice_updated(forkchoice_state, payload_attributes, self.log()) + .notify_forkchoice_updated(forkchoice_state, payload_attributes) .await }) .await; @@ -1538,7 +1523,6 @@ impl ExecutionLayer { process_payload_status( head_block_hash, result.map(|response| response.payload_status), - self.log(), ) .map_err(Box::new) .map_err(Error::EngineError) @@ -1635,11 +1619,10 @@ impl ExecutionLayer { if let Some(hash) = &hash_opt { info!( - self.log(), - "Found terminal block hash"; - "terminal_block_hash_override" => ?spec.terminal_block_hash, - "terminal_total_difficulty" => ?spec.terminal_total_difficulty, - "block_hash" => ?hash, + terminal_block_hash_override = ?spec.terminal_block_hash, + terminal_total_difficulty = ?spec.terminal_total_difficulty, + block_hash = ?hash, + "Found terminal block hash" ); } @@ -1669,7 +1652,8 @@ impl ExecutionLayer { self.execution_blocks().await.put(block.block_hash, block); loop { - let block_reached_ttd = block.total_difficulty >= spec.terminal_total_difficulty; + let block_reached_ttd = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd { if block.parent_hash == ExecutionBlockHash::zero() { return Ok(Some(block)); @@ -1678,7 +1662,8 @@ impl ExecutionLayer { .get_pow_block(engine, block.parent_hash) .await? .ok_or(ApiError::ExecutionBlockNotFound(block.parent_hash))?; - let parent_reached_ttd = parent.total_difficulty >= spec.terminal_total_difficulty; + let parent_reached_ttd = + parent.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd && !parent_reached_ttd { return Ok(Some(block)); @@ -1754,9 +1739,11 @@ impl ExecutionLayer { parent: ExecutionBlock, spec: &ChainSpec, ) -> bool { - let is_total_difficulty_reached = block.total_difficulty >= spec.terminal_total_difficulty; - let is_parent_total_difficulty_valid = - parent.total_difficulty < spec.terminal_total_difficulty; + let is_total_difficulty_reached = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); + let is_parent_total_difficulty_valid = parent + .total_difficulty + .is_some_and(|td| td < spec.terminal_total_difficulty); is_total_difficulty_reached && is_parent_total_difficulty_valid } @@ -1892,16 +1879,18 @@ impl ExecutionLayer { block_root: Hash256, block: &SignedBlindedBeaconBlock, ) -> Result, Error> { - debug!( - self.log(), - "Sending block to builder"; - "root" => ?block_root, - ); + debug!(?block_root, "Sending block to builder"); if let Some(builder) = self.builder() { let (payload_result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async { - if builder.is_ssz_enabled() { + let ssz_enabled = builder.is_ssz_available(); + debug!( + ?block_root, + ssz = ssz_enabled, + "Calling submit_blinded_block on builder" + ); + if ssz_enabled { builder .post_builder_blinded_blocks_ssz(block) .await @@ -1924,13 +1913,12 @@ impl ExecutionLayer { ); let payload = unblinded_response.payload_ref(); info!( - self.log(), - "Builder successfully revealed payload"; - "relay_response_ms" => duration.as_millis(), - "block_root" => ?block_root, - "fee_recipient" => ?payload.fee_recipient(), - "block_hash" => ?payload.block_hash(), - "parent_hash" => ?payload.parent_hash() + relay_response_ms = duration.as_millis(), + ?block_root, + fee_recipient = ?payload.fee_recipient(), + block_hash = ?payload.block_hash(), + parent_hash = ?payload.parent_hash(), + "Builder successfully revealed payload" ) } Err(e) => { @@ -1939,17 +1927,16 @@ impl ExecutionLayer { &[metrics::FAILURE], ); warn!( - self.log(), - "Builder failed to reveal payload"; - "info" => "this is common behaviour for some builders and may not indicate an issue", - "error" => ?e, - "relay_response_ms" => duration.as_millis(), - "block_root" => ?block_root, - "parent_hash" => ?block + info = "this is common behaviour for some builders and may not indicate an issue", + error = ?e, + relay_response_ms = duration.as_millis(), + ?block_root, + parent_hash = ?block .message() .execution_payload() .map(|payload| format!("{}", payload.parent_hash())) - .unwrap_or_else(|_| "unknown".to_string()) + .unwrap_or_else(|_| "unknown".to_string()), + "Builder failed to reveal payload" ) } } diff --git a/beacon_node/execution_layer/src/payload_status.rs b/beacon_node/execution_layer/src/payload_status.rs index cf0be8ed0d..bbfd30239d 100644 --- a/beacon_node/execution_layer/src/payload_status.rs +++ b/beacon_node/execution_layer/src/payload_status.rs @@ -1,6 +1,6 @@ use crate::engine_api::{Error as ApiError, PayloadStatusV1, PayloadStatusV1Status}; use crate::engines::EngineError; -use slog::{warn, Logger}; +use tracing::warn; use types::ExecutionBlockHash; /// Provides a simpler, easier to parse version of `PayloadStatusV1` for upstream users. @@ -26,15 +26,10 @@ pub enum PayloadStatus { pub fn process_payload_status( head_block_hash: ExecutionBlockHash, status: Result, - log: &Logger, ) -> Result { match status { Err(error) => { - warn!( - log, - "Error whilst processing payload status"; - "error" => ?error, - ); + warn!(?error, "Error whilst processing payload status"); Err(error) } Ok(response) => match &response.status { @@ -66,10 +61,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } @@ -82,10 +76,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } @@ -96,10 +89,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 9fa375b375..81fb9bd7b8 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -84,14 +84,14 @@ impl Block { block_hash: block.block_hash, block_number: block.block_number, parent_hash: block.parent_hash, - total_difficulty: block.total_difficulty, + total_difficulty: Some(block.total_difficulty), timestamp: block.timestamp, }, Block::PoS(payload) => ExecutionBlock { block_hash: payload.block_hash(), block_number: payload.block_number(), parent_hash: payload.parent_hash(), - total_difficulty, + total_difficulty: Some(total_difficulty), timestamp: payload.timestamp(), }, } @@ -448,7 +448,7 @@ impl ExecutionBlockGenerator { if self .head_block .as_ref() - .map_or(true, |head| head.block_hash() == last_block_hash) + .is_none_or(|head| head.block_hash() == last_block_hash) { self.head_block = Some(block.clone()); } diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index f07ee7ac6f..fba34121a7 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -13,7 +13,6 @@ use eth2::{ use fork_choice::ForkchoiceUpdateParameters; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; -use slog::{debug, error, info, warn, Logger}; use ssz::Encode; use std::collections::HashMap; use std::fmt::Debug; @@ -24,6 +23,7 @@ use std::time::Duration; use task_executor::TaskExecutor; use tempfile::NamedTempFile; use tokio_stream::StreamExt; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, @@ -309,7 +309,6 @@ pub struct MockBuilder { max_bid: bool, /// A cache that stores the proposers index for a given epoch proposers_cache: Arc>>>, - log: Logger, } impl MockBuilder { @@ -331,8 +330,7 @@ impl MockBuilder { ..Default::default() }; - let el = - ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap(); + let el = ExecutionLayer::from_config(config, executor.clone()).unwrap(); let builder = MockBuilder::new( el, @@ -342,7 +340,6 @@ impl MockBuilder { false, spec, None, - executor.log().clone(), ); let host: Ipv4Addr = Ipv4Addr::LOCALHOST; let port = 0; @@ -359,16 +356,12 @@ impl MockBuilder { max_bid: bool, spec: Arc, sk: Option<&[u8]>, - log: Logger, ) -> Self { let builder_sk = if let Some(sk_bytes) = sk { match SecretKey::deserialize(sk_bytes) { Ok(sk) => sk, Err(_) => { - error!( - log, - "Invalid sk_bytes provided, generating random secret key" - ); + error!("Invalid sk_bytes provided, generating random secret key"); SecretKey::random() } } @@ -390,7 +383,6 @@ impl MockBuilder { apply_operations, max_bid, genesis_time: None, - log, } } @@ -425,18 +417,13 @@ impl MockBuilder { &self, registrations: Vec, ) -> Result<(), String> { - info!( - self.log, - "Registering validators"; - "count" => registrations.len(), - ); + info!(count = registrations.len(), "Registering validators"); for registration in registrations { if !registration.verify_signature(&self.spec) { error!( - self.log, - "Failed to register validator"; - "error" => "invalid signature", - "validator" => %registration.message.pubkey + error = "invalid signature", + validator = %registration.message.pubkey, + "Failed to register validator" ); return Err("invalid signature".to_string()); } @@ -472,9 +459,8 @@ impl MockBuilder { } }; info!( - self.log, - "Submitting blinded beacon block to builder"; - "block_hash" => %root + block_hash = %root, + "Submitting blinded beacon block to builder" ); let payload = self .el @@ -486,10 +472,9 @@ impl MockBuilder { .try_into_full_block(Some(payload.clone())) .ok_or("Internal error, just provided a payload")?; debug!( - self.log, - "Got full payload, sending to local beacon node for propagation"; - "txs_count" => payload.transactions().len(), - "blob_count" => blobs.as_ref().map(|b| b.commitments.len()) + txs_count = payload.transactions().len(), + blob_count = blobs.as_ref().map(|b| b.commitments.len()), + "Got full payload, sending to local beacon node for propagation" ); let publish_block_request = PublishBlockRequest::new( Arc::new(full_block), @@ -508,7 +493,7 @@ impl MockBuilder { parent_hash: ExecutionBlockHash, pubkey: PublicKeyBytes, ) -> Result, String> { - info!(self.log, "In get_header"); + info!("In get_header"); // Check if the pubkey has registered with the builder if required if self.validate_pubkey && !self.val_registration_cache.read().contains_key(&pubkey) { return Err("validator not registered with builder".to_string()); @@ -521,15 +506,12 @@ impl MockBuilder { let payload_parameters = match payload_parameters { Some(params) => params, None => { - warn!( - self.log, - "Payload params not cached for parent_hash {}", parent_hash - ); + warn!("Payload params not cached for parent_hash {}", parent_hash); self.get_payload_params(slot, None, pubkey, None).await? } }; - info!(self.log, "Got payload params"); + info!("Got payload params"); let fork = self.fork_name_at_slot(slot); let payload_response_type = self @@ -545,7 +527,7 @@ impl MockBuilder { .await .map_err(|e| format!("couldn't get payload {:?}", e))?; - info!(self.log, "Got payload message, fork {}", fork); + info!("Got payload message, fork {}", fork); let mut message = match payload_response_type { crate::GetPayloadResponseType::Full(payload_response) => { @@ -616,10 +598,10 @@ impl MockBuilder { }; if self.apply_operations { - info!(self.log, "Applying operations"); + info!("Applying operations"); self.apply_operations(&mut message); } - info!(self.log, "Signing builder message"); + info!("Signing builder message"); let mut signature = message.sign_builder_message(&self.builder_sk, &self.spec); @@ -627,7 +609,7 @@ impl MockBuilder { signature = Signature::empty(); }; let signed_bid = SignedBuilderBid { message, signature }; - info!(self.log, "Builder bid {:?}", &signed_bid.message.value()); + info!("Builder bid {:?}", &signed_bid.message.value()); Ok(signed_bid) } @@ -648,10 +630,7 @@ impl MockBuilder { /// Prepare the execution layer for payload creation every slot for the correct /// proposer index pub async fn prepare_execution_layer(&self) -> Result<(), String> { - info!( - self.log, - "Starting a task to prepare the execution layer"; - ); + info!("Starting a task to prepare the execution layer"); let mut head_event_stream = self .beacon_client .get_events::(&[EventTopic::Head]) @@ -662,9 +641,8 @@ impl MockBuilder { match event { EventKind::Head(head) => { debug!( - self.log, - "Got a new head event"; - "block_hash" => %head.block + block_hash = %head.block, + "Got a new head event" ); let next_slot = head.slot + 1; // Find the next proposer index from the cached data or through a beacon api call @@ -712,9 +690,8 @@ impl MockBuilder { } e => { warn!( - self.log, - "Got an unexpected event"; - "event" => %e.topic_name() + event = %e.topic_name(), + "Got an unexpected event" ); } } @@ -812,7 +789,6 @@ impl MockBuilder { ), None => { warn!( - self.log, "Validator not registered {}, using default fee recipient and gas limits", pubkey ); diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index f45bfda9ff..cbe5e3ae98 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -76,8 +76,7 @@ impl MockExecutionLayer { suggested_fee_recipient: Some(Address::repeat_byte(42)), ..Default::default() }; - let el = - ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap(); + let el = ExecutionLayer::from_config(config, executor.clone()).unwrap(); Self { server, diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 75ff435886..17441a15fb 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -9,11 +9,11 @@ use bytes::Bytes; use execution_block_generator::PoWBlock; use handle_rpc::handle_rpc; use kzg::Kzg; -use logging::test_logger; + +use logging::create_test_tracing_subscriber; use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use serde::{Deserialize, Serialize}; use serde_json::json; -use slog::{info, Logger}; use std::collections::HashMap; use std::convert::Infallible; use std::future::Future; @@ -21,6 +21,7 @@ use std::marker::PhantomData; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::{Arc, LazyLock}; use tokio::{runtime, sync::oneshot}; +use tracing::info; use types::{ChainSpec, EthSpec, ExecutionBlockHash, Uint256}; use warp::{http::StatusCode, Filter, Rejection}; @@ -133,6 +134,7 @@ impl MockServer { spec: Arc, kzg: Option>, ) -> Self { + create_test_tracing_subscriber(); let MockExecutionConfig { jwt_key, terminal_difficulty, @@ -161,7 +163,6 @@ impl MockServer { let ctx: Arc> = Arc::new(Context { config: server_config, jwt_key, - log: test_logger(), last_echo_request: last_echo_request.clone(), execution_block_generator: RwLock::new(execution_block_generator), previous_request: <_>::default(), @@ -533,7 +534,7 @@ impl warp::reject::Reject for AuthError {} pub struct Context { pub config: Config, pub jwt_key: JwtKey, - pub log: Logger, + pub last_echo_request: Arc>>, pub execution_block_generator: RwLock>, pub preloaded_responses: Arc>>, @@ -671,7 +672,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); let inner_ctx = ctx.clone(); let ctx_filter = warp::any().map(move || inner_ctx.clone()); @@ -751,9 +751,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/beacon_node/genesis/Cargo.toml b/beacon_node/genesis/Cargo.toml index eeca393947..6ba8998a01 100644 --- a/beacon_node/genesis/Cargo.toml +++ b/beacon_node/genesis/Cargo.toml @@ -6,6 +6,7 @@ edition = { workspace = true } [dev-dependencies] eth1_test_rig = { workspace = true } +logging = { workspace = true } sensitive_url = { workspace = true } [dependencies] @@ -17,8 +18,8 @@ futures = { workspace = true } int_to_bytes = { workspace = true } merkle_proof = { workspace = true } rayon = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } diff --git a/beacon_node/genesis/src/eth1_genesis_service.rs b/beacon_node/genesis/src/eth1_genesis_service.rs index b5f4bd50ee..dede96512c 100644 --- a/beacon_node/genesis/src/eth1_genesis_service.rs +++ b/beacon_node/genesis/src/eth1_genesis_service.rs @@ -2,7 +2,6 @@ pub use crate::common::genesis_deposits; pub use eth1::Config as Eth1Config; use eth1::{DepositLog, Eth1Block, Service as Eth1Service}; -use slog::{debug, error, info, trace, Logger}; use state_processing::{ eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state, per_block_processing::process_operations::apply_deposit, process_activations, @@ -13,6 +12,7 @@ use std::sync::{ }; use std::time::Duration; use tokio::time::sleep; +use tracing::{debug, error, info, trace}; use types::{BeaconState, ChainSpec, Deposit, Eth1Data, EthSpec, FixedBytesExtended, Hash256}; /// The number of blocks that are pulled per request whilst waiting for genesis. @@ -43,7 +43,7 @@ impl Eth1GenesisService { /// Creates a new service. Does not attempt to connect to the Eth1 node. /// /// Modifies the given `config` to make it more suitable to the task of listening to genesis. - pub fn new(config: Eth1Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Eth1Config, spec: Arc) -> Result { let config = Eth1Config { // Truncating the block cache makes searching for genesis more // complicated. @@ -65,7 +65,7 @@ impl Eth1GenesisService { }; Ok(Self { - eth1_service: Eth1Service::new(config, log, spec) + eth1_service: Eth1Service::new(config, spec) .map_err(|e| format!("Failed to create eth1 service: {:?}", e))?, stats: Arc::new(Statistics { highest_processed_block: AtomicU64::new(0), @@ -103,15 +103,11 @@ impl Eth1GenesisService { ) -> Result, String> { let eth1_service = &self.eth1_service; let spec = eth1_service.chain_spec(); - let log = ð1_service.log; let mut sync_blocks = false; let mut highest_processed_block = None; - info!( - log, - "Importing eth1 deposit logs"; - ); + info!("Importing eth1 deposit logs"); loop { let update_result = eth1_service @@ -120,11 +116,7 @@ impl Eth1GenesisService { .map_err(|e| format!("{:?}", e)); if let Err(e) = update_result { - error!( - log, - "Failed to update eth1 deposit cache"; - "error" => e - ) + error!(error = e, "Failed to update eth1 deposit cache") } self.stats @@ -135,19 +127,15 @@ impl Eth1GenesisService { if let Some(viable_eth1_block) = self .first_candidate_eth1_block(spec.min_genesis_active_validator_count as usize) { - info!( - log, - "Importing eth1 blocks"; - ); + info!("Importing eth1 blocks"); self.eth1_service.set_lowest_cached_block(viable_eth1_block); sync_blocks = true } else { info!( - log, - "Waiting for more deposits"; - "min_genesis_active_validators" => spec.min_genesis_active_validator_count, - "total_deposits" => eth1_service.deposit_cache_len(), - "valid_deposits" => eth1_service.get_raw_valid_signature_count(), + min_genesis_active_validators = spec.min_genesis_active_validator_count, + total_deposits = eth1_service.deposit_cache_len(), + valid_deposits = eth1_service.get_raw_valid_signature_count(), + "Waiting for more deposits" ); sleep(update_interval).await; @@ -160,19 +148,17 @@ impl Eth1GenesisService { let blocks_imported = match eth1_service.update_block_cache(None).await { Ok(outcome) => { debug!( - log, - "Imported eth1 blocks"; - "latest_block_timestamp" => eth1_service.latest_block_timestamp(), - "cache_head" => eth1_service.highest_safe_block(), - "count" => outcome.blocks_imported, + latest_block_timestamp = eth1_service.latest_block_timestamp(), + cache_head = eth1_service.highest_safe_block(), + count = outcome.blocks_imported, + "Imported eth1 blocks" ); outcome.blocks_imported } Err(e) => { error!( - log, - "Failed to update eth1 block cache"; - "error" => format!("{:?}", e) + error = ?e, + "Failed to update eth1 block cache" ); 0 } @@ -183,13 +169,12 @@ impl Eth1GenesisService { self.scan_new_blocks::(&mut highest_processed_block, spec)? { info!( - log, - "Genesis ceremony complete"; - "genesis_validators" => genesis_state + genesis_validators = genesis_state .get_active_validator_indices(E::genesis_epoch(), spec) .map_err(|e| format!("Genesis validators error: {:?}", e))? .len(), - "genesis_time" => genesis_state.genesis_time(), + genesis_time = genesis_state.genesis_time(), + "Genesis ceremony complete" ); break Ok(genesis_state); } @@ -207,21 +192,19 @@ impl Eth1GenesisService { // Indicate that we are awaiting adequate active validators. if (active_validator_count as u64) < spec.min_genesis_active_validator_count { info!( - log, - "Waiting for more validators"; - "min_genesis_active_validators" => spec.min_genesis_active_validator_count, - "active_validators" => active_validator_count, - "total_deposits" => total_deposit_count, - "valid_deposits" => eth1_service.get_valid_signature_count().unwrap_or(0), + min_genesis_active_validators = spec.min_genesis_active_validator_count, + active_validators = active_validator_count, + total_deposits = total_deposit_count, + valid_deposits = eth1_service.get_valid_signature_count().unwrap_or(0), + "Waiting for more validators" ); } } else { info!( - log, - "Waiting for adequate eth1 timestamp"; - "genesis_delay" => spec.genesis_delay, - "genesis_time" => spec.min_genesis_time, - "latest_eth1_timestamp" => latest_timestamp, + genesis_delay = spec.genesis_delay, + genesis_time = spec.min_genesis_time, + latest_eth1_timestamp = latest_timestamp, + "Waiting for adequate eth1 timestamp" ); } @@ -253,7 +236,6 @@ impl Eth1GenesisService { spec: &ChainSpec, ) -> Result>, String> { let eth1_service = &self.eth1_service; - let log = ð1_service.log; for block in eth1_service.blocks().read().iter() { // It's possible that the block and deposit caches aren't synced. Ignore any blocks @@ -263,7 +245,7 @@ impl Eth1GenesisService { // again later. if eth1_service .highest_safe_block() - .map_or(true, |n| block.number > n) + .is_none_or(|n| block.number > n) { continue; } @@ -286,12 +268,11 @@ impl Eth1GenesisService { // Ignore any block with an insufficient timestamp. if !timestamp_can_trigger_genesis(block.timestamp, spec)? { trace!( - log, - "Insufficient block timestamp"; - "genesis_delay" => spec.genesis_delay, - "min_genesis_time" => spec.min_genesis_time, - "eth1_block_timestamp" => block.timestamp, - "eth1_block_number" => block.number, + genesis_delay = spec.genesis_delay, + min_genesis_time = spec.min_genesis_time, + eth1_block_timestamp = block.timestamp, + eth1_block_number = block.number, + "Insufficient block timestamp" ); continue; } @@ -301,12 +282,11 @@ impl Eth1GenesisService { .unwrap_or(0); if (valid_signature_count as u64) < spec.min_genesis_active_validator_count { trace!( - log, - "Insufficient valid signatures"; - "genesis_delay" => spec.genesis_delay, - "valid_signature_count" => valid_signature_count, - "min_validator_count" => spec.min_genesis_active_validator_count, - "eth1_block_number" => block.number, + genesis_delay = spec.genesis_delay, + valid_signature_count = valid_signature_count, + min_validator_count = spec.min_genesis_active_validator_count, + eth1_block_number = block.number, + "Insufficient valid signatures" ); continue; } @@ -333,11 +313,11 @@ impl Eth1GenesisService { return Ok(Some(genesis_state)); } else { trace!( - log, - "Insufficient active validators"; - "min_genesis_active_validator_count" => format!("{}", spec.min_genesis_active_validator_count), - "active_validators" => active_validator_count, - "eth1_block_number" => block.number, + min_genesis_active_validator_count = + format!("{}", spec.min_genesis_active_validator_count), + active_validators = active_validator_count, + eth1_block_number = block.number, + "Insufficient active validators" ); } } diff --git a/beacon_node/genesis/tests/tests.rs b/beacon_node/genesis/tests/tests.rs index 6cc7517aa4..b5710e50fd 100644 --- a/beacon_node/genesis/tests/tests.rs +++ b/beacon_node/genesis/tests/tests.rs @@ -3,6 +3,7 @@ use environment::{Environment, EnvironmentBuilder}; use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; use eth1_test_rig::{AnvilEth1Instance, DelayThenDeposit, Middleware}; use genesis::{Eth1Config, Eth1GenesisService}; +use logging::create_test_tracing_subscriber; use sensitive_url::SensitiveUrl; use state_processing::is_valid_genesis_state; use std::sync::Arc; @@ -12,11 +13,10 @@ use types::{ }; pub fn new_env() -> Environment { + create_test_tracing_subscriber(); EnvironmentBuilder::minimal() .multi_threaded_tokio_runtime() .expect("should start tokio runtime") - .test_logger() - .expect("should start null logger") .build() .expect("should build env") } @@ -24,7 +24,6 @@ pub fn new_env() -> Environment { #[test] fn basic() { let env = new_env(); - let log = env.core_context().log().clone(); let mut spec = (*env.eth2_config().spec).clone(); spec.min_genesis_time = 0; spec.min_genesis_active_validator_count = 8; @@ -55,7 +54,6 @@ fn basic() { block_cache_truncation: None, ..Eth1Config::default() }, - log, spec.clone(), ) .unwrap(); diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index 2fb3ec06bf..b13517f27e 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -33,7 +33,6 @@ safe_arith = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } state_processing = { workspace = true } store = { workspace = true } @@ -42,6 +41,7 @@ system_health = { path = "../../common/system_health" } task_executor = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } warp = { workspace = true } @@ -49,9 +49,7 @@ warp_utils = { workspace = true } [dev-dependencies] genesis = { workspace = true } -logging = { workspace = true } proto_array = { workspace = true } -serde_json = { workspace = true } [[test]] name = "bn_http_api_tests" diff --git a/beacon_node/http_api/src/aggregate_attestation.rs b/beacon_node/http_api/src/aggregate_attestation.rs index 94b6acd2e6..23af5b0cb5 100644 --- a/beacon_node/http_api/src/aggregate_attestation.rs +++ b/beacon_node/http_api/src/aggregate_attestation.rs @@ -18,13 +18,14 @@ pub fn get_aggregate_attestation( endpoint_version: EndpointVersion, chain: Arc>, ) -> Result, warp::reject::Rejection> { - if endpoint_version == V2 { + let fork_name = chain.spec.fork_name_at_slot::(slot); + let aggregate_attestation = if fork_name.electra_enabled() { let Some(committee_index) = committee_index else { return Err(warp_utils::reject::custom_bad_request( "missing committee index".to_string(), )); }; - let aggregate_attestation = chain + chain .get_aggregated_attestation_electra(slot, attestation_data_root, committee_index) .map_err(|e| { warp_utils::reject::custom_bad_request(format!( @@ -34,8 +35,22 @@ pub fn get_aggregate_attestation( })? .ok_or_else(|| { warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) - })?; - let fork_name = chain.spec.fork_name_at_slot::(slot); + })? + } else { + chain + .get_pre_electra_aggregated_attestation_by_slot_and_root(slot, attestation_data_root) + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "unable to fetch aggregate: {:?}", + e + )) + })? + .ok_or_else(|| { + warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) + })? + }; + + if endpoint_version == V2 { let fork_versioned_response = ForkVersionedResponse { version: Some(fork_name), metadata: EmptyMetadata {}, @@ -46,19 +61,7 @@ pub fn get_aggregate_attestation( fork_name, )) } else if endpoint_version == V1 { - let aggregate_attestation = chain - .get_pre_electra_aggregated_attestation_by_slot_and_root(slot, attestation_data_root) - .map_err(|e| { - warp_utils::reject::custom_bad_request(format!( - "unable to fetch aggregate: {:?}", - e - )) - })? - .map(GenericResponse::from) - .ok_or_else(|| { - warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) - })?; - Ok(warp::reply::json(&aggregate_attestation).into_response()) + Ok(warp::reply::json(&GenericResponse::from(aggregate_attestation)).into_response()) } else { return Err(unsupported_version_rejection(endpoint_version)); } diff --git a/beacon_node/http_api/src/attestation_performance.rs b/beacon_node/http_api/src/attestation_performance.rs index 2f3f340445..23ab5e3752 100644 --- a/beacon_node/http_api/src/attestation_performance.rs +++ b/beacon_node/http_api/src/attestation_performance.rs @@ -126,8 +126,11 @@ pub fn get_attestation_performance( // Load state for block replay. let state_root = prior_block.state_root(); + + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let state = chain - .get_state(&state_root, Some(prior_slot)) + .get_state(&state_root, Some(prior_slot), true) .and_then(|maybe_state| maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root))) .map_err(unhandled_error)?; diff --git a/beacon_node/http_api/src/block_packing_efficiency.rs b/beacon_node/http_api/src/block_packing_efficiency.rs index 431547f10b..249a6732dc 100644 --- a/beacon_node/http_api/src/block_packing_efficiency.rs +++ b/beacon_node/http_api/src/block_packing_efficiency.rs @@ -285,8 +285,10 @@ pub fn get_block_packing_efficiency( // Load state for block replay. let starting_state_root = first_block.state_root(); + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let starting_state = chain - .get_state(&starting_state_root, Some(prior_slot)) + .get_state(&starting_state_root, Some(prior_slot), true) .and_then(|maybe_state| { maybe_state.ok_or(BeaconChainError::MissingBeaconState(starting_state_root)) }) diff --git a/beacon_node/http_api/src/block_rewards.rs b/beacon_node/http_api/src/block_rewards.rs index 0cc878bb48..29b23e89a7 100644 --- a/beacon_node/http_api/src/block_rewards.rs +++ b/beacon_node/http_api/src/block_rewards.rs @@ -1,10 +1,10 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; use eth2::lighthouse::{BlockReward, BlockRewardsQuery}; use lru::LruCache; -use slog::{debug, warn, Logger}; use state_processing::BlockReplayer; use std::num::NonZeroUsize; use std::sync::Arc; +use tracing::{debug, warn}; use types::beacon_block::BlindedBeaconBlock; use types::non_zero_usize::new_non_zero_usize; use warp_utils::reject::{beacon_state_error, custom_bad_request, unhandled_error}; @@ -15,7 +15,6 @@ const STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(2); pub fn get_block_rewards( query: BlockRewardsQuery, chain: Arc>, - log: Logger, ) -> Result, warp::Rejection> { let start_slot = query.start_slot; let end_slot = query.end_slot; @@ -43,8 +42,10 @@ pub fn get_block_rewards( .map_err(unhandled_error)? .ok_or_else(|| custom_bad_request(format!("prior state at slot {} unknown", prior_slot)))?; + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let mut state = chain - .get_state(&state_root, Some(prior_slot)) + .get_state(&state_root, Some(prior_slot), true) .and_then(|maybe_state| maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root))) .map_err(unhandled_error)?; @@ -81,12 +82,7 @@ pub fn get_block_rewards( .map_err(unhandled_error)?; if block_replayer.state_root_miss() { - warn!( - log, - "Block reward state root miss"; - "start_slot" => start_slot, - "end_slot" => end_slot, - ); + warn!(%start_slot, %end_slot, "Block reward state root miss"); } drop(block_replayer); @@ -98,7 +94,6 @@ pub fn get_block_rewards( pub fn compute_block_rewards( blocks: Vec>, chain: Arc>, - log: Logger, ) -> Result, warp::Rejection> { let mut block_rewards = Vec::with_capacity(blocks.len()); let mut state_cache = LruCache::new(STATE_CACHE_SIZE); @@ -110,18 +105,16 @@ pub fn compute_block_rewards( // Check LRU cache for a constructed state from a previous iteration. let state = if let Some(state) = state_cache.get(&(parent_root, block.slot())) { debug!( - log, - "Re-using cached state for block rewards"; - "parent_root" => ?parent_root, - "slot" => block.slot(), + ?parent_root, + slot = %block.slot(), + "Re-using cached state for block rewards" ); state } else { debug!( - log, - "Fetching state for block rewards"; - "parent_root" => ?parent_root, - "slot" => block.slot() + ?parent_root, + slot = %block.slot(), + "Fetching state for block rewards" ); let parent_block = chain .get_blinded_block(&parent_root) @@ -133,8 +126,10 @@ pub fn compute_block_rewards( )) })?; + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let parent_state = chain - .get_state(&parent_block.state_root(), Some(parent_block.slot())) + .get_state(&parent_block.state_root(), Some(parent_block.slot()), true) .map_err(unhandled_error)? .ok_or_else(|| { custom_bad_request(format!( @@ -152,10 +147,9 @@ pub fn compute_block_rewards( if block_replayer.state_root_miss() { warn!( - log, - "Block reward state root miss"; - "parent_slot" => parent_block.slot(), - "slot" => block.slot(), + parent_slot = %parent_block.slot(), + slot = %block.slot(), + "Block reward state root miss" ); } diff --git a/beacon_node/http_api/src/database.rs b/beacon_node/http_api/src/database.rs index aa8b0e8ffc..8a50ec45b0 100644 --- a/beacon_node/http_api/src/database.rs +++ b/beacon_node/http_api/src/database.rs @@ -1,7 +1,17 @@ use beacon_chain::store::metadata::CURRENT_SCHEMA_VERSION; use beacon_chain::{BeaconChain, BeaconChainTypes}; -use eth2::lighthouse::DatabaseInfo; +use serde::Serialize; use std::sync::Arc; +use store::{AnchorInfo, BlobInfo, Split, StoreConfig}; + +#[derive(Debug, Serialize)] +pub struct DatabaseInfo { + pub schema_version: u64, + pub config: StoreConfig, + pub split: Split, + pub anchor: AnchorInfo, + pub blob_info: BlobInfo, +} pub fn info( chain: Arc>, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index d6431fe729..eb188a9b19 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -16,6 +16,7 @@ mod builder_states; mod database; mod light_client; mod metrics; +mod peer; mod produce_block; mod proposer_duties; mod publish_attestations; @@ -53,9 +54,9 @@ use eth2::types::{ use eth2::{CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER}; use health_metrics::observe::Observe; use lighthouse_network::rpc::methods::MetaData; -use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; +use lighthouse_network::{types::SyncState, Enr, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_version::version_with_platform; -use logging::SSELoggingComponents; +use logging::{crit, SSELoggingComponents}; use network::{NetworkMessage, NetworkSenders, ValidatorSubscriptionMessage}; use operation_pool::ReceivedPreCapella; use parking_lot::RwLock; @@ -64,7 +65,6 @@ pub use publish_blocks::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; pub use state_id::StateId; @@ -72,6 +72,7 @@ use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::pin::Pin; +use std::str::FromStr; use std::sync::Arc; use sysinfo::{System, SystemExt}; use system_health::{observe_nat, observe_system_health_bn}; @@ -84,13 +85,14 @@ use tokio_stream::{ wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}, StreamExt, }; +use tracing::{debug, error, info, warn}; use types::{ fork_versioned_response::EmptyMetadata, Attestation, AttestationData, AttestationShufflingId, - AttesterSlashing, BeaconStateError, ChainSpec, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, - ForkName, ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing, - RelativeEpoch, SignedAggregateAndProof, SignedBlindedBeaconBlock, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, - SyncCommitteeMessage, SyncContributionData, + AttesterSlashing, BeaconStateError, ChainSpec, Checkpoint, CommitteeCache, ConfigAndPreset, + Epoch, EthSpec, ForkName, ForkVersionedResponse, Hash256, ProposerPreparationData, + ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, SignedBlindedBeaconBlock, + SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData, + SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, }; use validator::pubkey_to_validator_index; use version::{ @@ -107,13 +109,6 @@ use warp_utils::{query::multi_key_query, reject::convert_rejection, uor::Unifyin const API_PREFIX: &str = "eth"; -/// If the node is within this many epochs from the head, we declare it to be synced regardless of -/// the network sync state. -/// -/// This helps prevent attacks where nodes can convince us that we're syncing some non-existent -/// finalized head. -const SYNC_TOLERANCE_EPOCHS: u64 = 8; - /// A custom type which allows for both unsecured and TLS-enabled HTTP servers. type HttpServer = (SocketAddr, Pin + Send>>); @@ -139,7 +134,6 @@ pub struct Context { pub beacon_processor_reprocess_send: Option>, pub eth1_service: Option, pub sse_logging_components: Option, - pub log: Logger, } /// Configuration for the HTTP server. @@ -155,7 +149,6 @@ pub struct Config { pub enable_beacon_processor: bool, #[serde(with = "eth2::types::serde_status_code")] pub duplicate_block_status_code: StatusCode, - pub enable_light_client_server: bool, pub target_peers: usize, } @@ -171,7 +164,6 @@ impl Default for Config { sse_capacity_multiplier: 1, enable_beacon_processor: true, duplicate_block_status_code: StatusCode::ACCEPTED, - enable_light_client_server: true, target_peers: 100, } } @@ -195,40 +187,6 @@ impl From for Error { } } -/// Creates a `warp` logging wrapper which we use to create `slog` logs. -pub fn slog_logging( - log: Logger, -) -> warp::filters::log::Log { - warp::log::custom(move |info| { - match info.status() { - status - if status == StatusCode::OK - || status == StatusCode::NOT_FOUND - || status == StatusCode::PARTIAL_CONTENT => - { - debug!( - log, - "Processed HTTP API request"; - "elapsed" => format!("{:?}", info.elapsed()), - "status" => status.to_string(), - "path" => info.path(), - "method" => info.method().to_string(), - ); - } - status => { - warn!( - log, - "Error processing HTTP API request"; - "elapsed" => format!("{:?}", info.elapsed()), - "status" => status.to_string(), - "path" => info.path(), - "method" => info.method().to_string(), - ); - } - }; - }) -} - /// Creates a `warp` logging wrapper which we use for Prometheus metrics (not necessarily logging, /// per say). pub fn prometheus_metrics() -> warp::filters::log::Log { @@ -296,18 +254,6 @@ pub fn prometheus_metrics() -> warp::filters::log::Log impl Filter + Clone { - warp::any() - .and_then(move || async move { - if is_enabled { - Ok(()) - } else { - Err(warp::reject::not_found()) - } - }) - .untuple_one() -} - /// Creates a server that will serve requests using information from `ctx`. /// /// The server will shut down gracefully when the `shutdown` future resolves. @@ -328,7 +274,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result { let config = ctx.config.clone(); - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -345,7 +290,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled HTTP server"); + crit!("Cannot start disabled HTTP server"); return Err(Error::Other( "A disabled server should not be started".to_string(), )); @@ -473,7 +418,8 @@ pub fn serve( ) })?; - let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch(); + let tolerance = + chain.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); if head_slot + tolerance >= current_slot { Ok(()) @@ -493,9 +439,17 @@ pub fn serve( }, ); - // Create a `warp` filter that provides access to the logger. - let inner_ctx = ctx.clone(); - let log_filter = warp::any().map(move || inner_ctx.log.clone()); + // Create a `warp` filter that returns 404s if the light client server is disabled. + let light_client_server_filter = + warp::any() + .and(chain_filter.clone()) + .then(|chain: Arc>| async move { + if chain.config.enable_light_client_server { + Ok(()) + } else { + Err(warp::reject::not_found()) + } + }); let inner_components = ctx.sse_logging_components.clone(); let sse_component_filter = warp::any().map(move || inner_components.clone()); @@ -1125,6 +1079,72 @@ pub fn serve( }, ); + // GET beacon/states/{state_id}/pending_deposits + let get_beacon_state_pending_deposits = beacon_states_path + .clone() + .and(warp::path("pending_deposits")) + .and(warp::path::end()) + .then( + |state_id: StateId, + task_spawner: TaskSpawner, + chain: Arc>| { + task_spawner.blocking_json_task(Priority::P1, move || { + let (data, execution_optimistic, finalized) = state_id + .map_state_and_execution_optimistic_and_finalized( + &chain, + |state, execution_optimistic, finalized| { + let Ok(deposits) = state.pending_deposits() else { + return Err(warp_utils::reject::custom_bad_request( + "Pending deposits not found".to_string(), + )); + }; + + Ok((deposits.clone(), execution_optimistic, finalized)) + }, + )?; + + Ok(api_types::ExecutionOptimisticFinalizedResponse { + data, + execution_optimistic: Some(execution_optimistic), + finalized: Some(finalized), + }) + }) + }, + ); + + // GET beacon/states/{state_id}/pending_partial_withdrawals + let get_beacon_state_pending_partial_withdrawals = beacon_states_path + .clone() + .and(warp::path("pending_partial_withdrawals")) + .and(warp::path::end()) + .then( + |state_id: StateId, + task_spawner: TaskSpawner, + chain: Arc>| { + task_spawner.blocking_json_task(Priority::P1, move || { + let (data, execution_optimistic, finalized) = state_id + .map_state_and_execution_optimistic_and_finalized( + &chain, + |state, execution_optimistic, finalized| { + let Ok(withdrawals) = state.pending_partial_withdrawals() else { + return Err(warp_utils::reject::custom_bad_request( + "Pending withdrawals not found".to_string(), + )); + }; + + Ok((withdrawals.clone(), execution_optimistic, finalized)) + }, + )?; + + Ok(api_types::ExecutionOptimisticFinalizedResponse { + data, + execution_optimistic: Some(execution_optimistic), + finalized: Some(finalized), + }) + }) + }, + ); + // GET beacon/headers // // Note: this endpoint only returns information about blocks in the canonical chain. Given that @@ -1292,21 +1312,18 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_contents: PublishBlockRequest, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1326,15 +1343,13 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_bytes: Bytes, consensus_version: ForkName, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block_contents = PublishBlockRequest::::from_ssz_bytes( &block_bytes, @@ -1348,7 +1363,6 @@ pub fn serve( ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1368,22 +1382,19 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_contents: PublishBlockRequest, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1404,7 +1415,6 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_bytes: Bytes, @@ -1412,8 +1422,7 @@ pub fn serve( task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block_contents = PublishBlockRequest::::from_ssz_bytes( &block_bytes, @@ -1427,7 +1436,6 @@ pub fn serve( ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1451,20 +1459,17 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_contents: Arc>, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_blinded_block( block_contents, chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1484,14 +1489,12 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_bytes: Bytes, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBlindedBeaconBlock::::from_ssz_bytes( &block_bytes, @@ -1505,7 +1508,6 @@ pub fn serve( block, chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1525,21 +1527,18 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, blinded_block: Arc>, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_blinded_block( blinded_block, chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1559,15 +1558,13 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_bytes: Bytes, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBlindedBeaconBlock::::from_ssz_bytes( &block_bytes, @@ -1581,7 +1578,6 @@ pub fn serve( block, chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1851,14 +1847,12 @@ pub fn serve( .and(warp_utils::json::json()) .and(network_tx_filter.clone()) .and(reprocess_send_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, attestations: Vec>, network_tx: UnboundedSender>, - reprocess_tx: Option>, - log: Logger| async move { + reprocess_tx: Option>| async move { let attestations = attestations.into_iter().map(Either::Left).collect(); let result = crate::publish_attestations::publish_attestations( task_spawner, @@ -1866,7 +1860,6 @@ pub fn serve( attestations, network_tx, reprocess_tx, - log, ) .await .map(|()| warp::reply::json(&())); @@ -1882,25 +1875,22 @@ pub fn serve( .and(optional_consensus_version_header_filter) .and(network_tx_filter.clone()) .and(reprocess_send_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, payload: Value, fork_name: Option, network_tx: UnboundedSender>, - reprocess_tx: Option>, - log: Logger| async move { + reprocess_tx: Option>| async move { let attestations = match crate::publish_attestations::deserialize_attestation_payload::( - payload, fork_name, &log, + payload, fork_name, ) { Ok(attestations) => attestations, Err(err) => { warn!( - log, - "Unable to deserialize attestation POST request"; - "error" => ?err + error = ?err, + "Unable to deserialize attestation POST request" ); return warp::reply::with_status( warp::reply::json( @@ -1918,7 +1908,6 @@ pub fn serve( attestations, network_tx, reprocess_tx, - log, ) .await .map(|()| warp::reply::json(&())); @@ -1939,10 +1928,10 @@ pub fn serve( query: api_types::AttestationPoolQuery| { task_spawner.blocking_response_task(Priority::P1, move || { let query_filter = |data: &AttestationData| { - query.slot.map_or(true, |slot| slot == data.slot) + query.slot.is_none_or(|slot| slot == data.slot) && query .committee_index - .map_or(true, |index| index == data.index) + .is_none_or(|index| index == data.index) }; let mut attestations = chain.op_pool.get_filtered_attestations(query_filter); @@ -2193,16 +2182,14 @@ pub fn serve( .and(warp::path::end()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, signatures: Vec, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { sync_committees::process_sync_committee_signatures( - signatures, network_tx, &chain, log, + signatures, network_tx, &chain, )?; Ok(api_types::GenericResponse::from(())) }) @@ -2230,13 +2217,11 @@ pub fn serve( .and(warp::path::end()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, address_changes: Vec, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { let mut failures = vec![]; @@ -2253,11 +2238,12 @@ pub fn serve( .to_execution_address; // New to P2P *and* op pool, gossip immediately if post-Capella. - let received_pre_capella = if chain.current_slot_is_post_capella().unwrap_or(false) { - ReceivedPreCapella::No - } else { - ReceivedPreCapella::Yes - }; + let received_pre_capella = + if chain.current_slot_is_post_capella().unwrap_or(false) { + ReceivedPreCapella::No + } else { + ReceivedPreCapella::Yes + }; if matches!(received_pre_capella, ReceivedPreCapella::No) { publish_pubsub_message( &network_tx, @@ -2268,32 +2254,29 @@ pub fn serve( } // Import to op pool (may return `false` if there's a race). - let imported = - chain.import_bls_to_execution_change(verified_address_change, received_pre_capella); + let imported = chain.import_bls_to_execution_change( + verified_address_change, + received_pre_capella, + ); info!( - log, - "Processed BLS to execution change"; - "validator_index" => validator_index, - "address" => ?address, - "published" => matches!(received_pre_capella, ReceivedPreCapella::No), - "imported" => imported, + %validator_index, + ?address, + published = + matches!(received_pre_capella, ReceivedPreCapella::No), + imported, + "Processed BLS to execution change" ); } Ok(ObservationOutcome::AlreadyKnown) => { - debug!( - log, - "BLS to execution change already known"; - "validator_index" => validator_index, - ); + debug!(%validator_index, "BLS to execution change already known"); } Err(e) => { warn!( - log, - "Invalid BLS to execution change"; - "validator_index" => validator_index, - "reason" => ?e, - "source" => "HTTP", + validator_index, + reason = ?e, + source = "HTTP", + "Invalid BLS to execution change" ); failures.push(api_types::Failure::new( index, @@ -2452,6 +2435,7 @@ pub fn serve( let beacon_light_client_path = eth_v1 .and(warp::path("beacon")) .and(warp::path("light_client")) + .and(light_client_server_filter) .and(chain_filter.clone()); // GET beacon/light_client/bootstrap/{block_root} @@ -2467,11 +2451,13 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, block_root: Hash256, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; get_light_client_bootstrap::(chain, &block_root, accept_header) }) }, @@ -2485,10 +2471,12 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; let update = chain .light_client_server_cache .get_latest_optimistic_update() @@ -2532,10 +2520,12 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; let update = chain .light_client_server_cache .get_latest_finality_update() @@ -2580,11 +2570,13 @@ pub fn serve( .and(warp::query::()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, query: LightClientUpdatesQuery, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; get_light_client_updates::(chain, query, accept_header) }) }, @@ -2657,17 +2649,15 @@ pub fn serve( .and(block_id_or_err) .and(warp::path::end()) .and(warp_utils::json::json()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, block_id: BlockId, - validators: Vec, - log: Logger| { + validators: Vec| { task_spawner.blocking_json_task(Priority::P1, move || { let (rewards, execution_optimistic, finalized) = sync_committee_rewards::compute_sync_committee_rewards( - chain, block_id, validators, log, + chain, block_id, validators, )?; Ok(api_types::GenericResponse::from(rewards)).map(|resp| { @@ -2754,14 +2744,12 @@ pub fn serve( .and(warp::header::optional::("accept")) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |endpoint_version: EndpointVersion, state_id: StateId, accept_header: Option, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_response_task(Priority::P1, move || match accept_header { Some(api_types::Accept::Ssz) => { // We can ignore the optimistic status for the "fork" since it's a @@ -2776,10 +2764,9 @@ pub fn serve( let response_bytes = state.as_ssz_bytes(); drop(timer); debug!( - log, - "HTTP state load"; - "total_time_ms" => t.elapsed().as_millis(), - "target_slot" => state.slot() + total_time_ms = t.elapsed().as_millis(), + target_slot = %state.slot(), + "HTTP state load" ); Response::builder() @@ -3103,15 +3090,13 @@ pub fn serve( }; // the eth2 API spec implies only peers we have been connected to at some point should be included. - if let Some(dir) = peer_info.connection_direction().as_ref() { + if let Some(&dir) = peer_info.connection_direction() { return Ok(api_types::GenericResponse::from(api_types::PeerData { peer_id: peer_id.to_string(), enr: peer_info.enr().map(|enr| enr.to_base64()), last_seen_p2p_address: address, - direction: api_types::PeerDirection::from_connection_direction(dir), - state: api_types::PeerState::from_peer_connection_status( - peer_info.connection_status(), - ), + direction: dir.into(), + state: peer_info.connection_status().clone().into(), })); } } @@ -3152,18 +3137,15 @@ pub fn serve( }; // the eth2 API spec implies only peers we have been connected to at some point should be included. - if let Some(dir) = peer_info.connection_direction() { - let direction = - api_types::PeerDirection::from_connection_direction(dir); - let state = api_types::PeerState::from_peer_connection_status( - peer_info.connection_status(), - ); + if let Some(&dir) = peer_info.connection_direction() { + let direction = dir.into(); + let state = peer_info.connection_status().clone().into(); - let state_matches = query.state.as_ref().map_or(true, |states| { + let state_matches = query.state.as_ref().is_none_or(|states| { states.iter().any(|state_param| *state_param == state) }); let direction_matches = - query.direction.as_ref().map_or(true, |directions| { + query.direction.as_ref().is_none_or(|directions| { directions.iter().any(|dir_param| *dir_param == direction) }); @@ -3209,9 +3191,8 @@ pub fn serve( .read() .peers() .for_each(|(_, peer_info)| { - let state = api_types::PeerState::from_peer_connection_status( - peer_info.connection_status(), - ); + let state = + api_types::PeerState::from(peer_info.connection_status().clone()); match state { api_types::PeerState::Connected => connected += 1, api_types::PeerState::Connecting => connecting += 1, @@ -3247,16 +3228,14 @@ pub fn serve( .and(not_while_syncing_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |epoch: Epoch, not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; - proposer_duties::proposer_duties(epoch, &chain, &log) + proposer_duties::proposer_duties(epoch, &chain) }) }, ); @@ -3276,7 +3255,6 @@ pub fn serve( .and(warp::query::()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |endpoint_version: EndpointVersion, slot: Slot, @@ -3284,14 +3262,9 @@ pub fn serve( not_synced_filter: Result<(), Rejection>, query: api_types::ValidatorBlocksQuery, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { - debug!( - log, - "Block production request from HTTP API"; - "slot" => slot - ); + debug!(?slot, "Block production request from HTTP API"); not_synced_filter?; @@ -3498,7 +3471,6 @@ pub fn serve( .and(chain_filter.clone()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( // V1 and V2 are identical except V2 has a consensus version header in the request. // We only require this header for SSZ deserialization, which isn't supported for @@ -3508,7 +3480,7 @@ pub fn serve( task_spawner: TaskSpawner, chain: Arc>, aggregates: Vec>, - network_tx: UnboundedSender>, log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; let seen_timestamp = timestamp_now(); @@ -3555,13 +3527,13 @@ pub fn serve( // aggregate has been successfully published by some other node. Err(AttnError::AggregatorAlreadyKnown(_)) => continue, Err(e) => { - error!(log, - "Failure verifying aggregate and proofs"; - "error" => format!("{:?}", e), - "request_index" => index, - "aggregator_index" => aggregate.message().aggregator_index(), - "attestation_index" => aggregate.message().aggregate().committee_index(), - "attestation_slot" => aggregate.message().aggregate().data().slot, + error!( + error = ?e, + request_index = index, + aggregator_index = aggregate.message().aggregator_index(), + attestation_index = aggregate.message().aggregate().committee_index(), + attestation_slot = %aggregate.message().aggregate().data().slot, + "Failure verifying aggregate and proofs" ); failures.push(api_types::Failure::new(index, format!("Verification: {:?}", e))); } @@ -3576,22 +3548,21 @@ pub fn serve( // Import aggregate attestations for (index, verified_aggregate) in verified_aggregates { if let Err(e) = chain.apply_attestation_to_fork_choice(&verified_aggregate) { - error!(log, - "Failure applying verified aggregate attestation to fork choice"; - "error" => format!("{:?}", e), - "request_index" => index, - "aggregator_index" => verified_aggregate.aggregate().message().aggregator_index(), - "attestation_index" => verified_aggregate.attestation().committee_index(), - "attestation_slot" => verified_aggregate.attestation().data().slot, + error!( + error = ?e, + request_index = index, + aggregator_index = verified_aggregate.aggregate().message().aggregator_index(), + attestation_index = verified_aggregate.attestation().committee_index(), + attestation_slot = %verified_aggregate.attestation().data().slot, + "Failure applying verified aggregate attestation to fork choice" ); failures.push(api_types::Failure::new(index, format!("Fork choice: {:?}", e))); } if let Err(e) = chain.add_to_block_inclusion_pool(verified_aggregate) { warn!( - log, - "Could not add verified aggregate attestation to the inclusion pool"; - "error" => ?e, - "request_index" => index, + error = ?e, + request_index = index, + "Could not add verified aggregate attestation to the inclusion pool" ); failures.push(api_types::Failure::new(index, format!("Op pool: {:?}", e))); } @@ -3616,22 +3587,19 @@ pub fn serve( .and(task_spawner_filter.clone()) .and(chain_filter.clone()) .and(warp_utils::json::json()) - .and(network_tx_filter) - .and(log_filter.clone()) + .and(network_tx_filter.clone()) .then( |not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, chain: Arc>, contributions: Vec>, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; sync_committees::process_signed_contribution_and_proofs( contributions, network_tx, &chain, - log, )?; Ok(api_types::GenericResponse::from(())) }) @@ -3647,13 +3615,11 @@ pub fn serve( .and(validator_subscription_tx_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |subscriptions: Vec, validator_subscription_tx: Sender, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_json_task(Priority::P0, move || { let subscriptions: std::collections::BTreeSet<_> = subscriptions .iter() @@ -3674,10 +3640,9 @@ pub fn serve( ValidatorSubscriptionMessage::AttestationSubscribe { subscriptions }; if let Err(e) = validator_subscription_tx.try_send(message) { warn!( - log, - "Unable to process committee subscriptions"; - "info" => "the host may be overloaded or resource-constrained", - "error" => ?e, + info = "the host may be overloaded or resource-constrained", + error = ?e, + "Unable to process committee subscriptions" ); return Err(warp_utils::reject::custom_server_error( "unable to queue subscription, host may be overloaded or shutting down" @@ -3698,13 +3663,11 @@ pub fn serve( .and(not_while_syncing_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .and(warp_utils::json::json()) .then( |not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, chain: Arc>, - log: Logger, preparation_data: Vec| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { not_synced_filter?; @@ -3718,9 +3681,8 @@ pub fn serve( let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); debug!( - log, - "Received proposer preparation data"; - "count" => preparation_data.len(), + count = preparation_data.len(), + "Received proposer preparation data" ); execution_layer @@ -3752,12 +3714,10 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .and(warp_utils::json::json()) .then( |task_spawner: TaskSpawner, chain: Arc>, - log: Logger, register_val_data: Vec| async { let (tx, rx) = oneshot::channel(); @@ -3776,9 +3736,8 @@ pub fn serve( let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); debug!( - log, - "Received register validator request"; - "count" => register_val_data.len(), + count = register_val_data.len(), + "Received register validator request" ); let head_snapshot = chain.head_snapshot(); @@ -3853,9 +3812,8 @@ pub fn serve( })?; info!( - log, - "Forwarding register validator request to connected builder"; - "count" => filtered_registration_data.len(), + count = filtered_registration_data.len(), + "Forwarding register validator request to connected builder" ); // It's a waste of a `BeaconProcessor` worker to just @@ -3880,10 +3838,9 @@ pub fn serve( .map(|resp| warp::reply::json(&resp).into_response()) .map_err(|e| { warn!( - log, - "Relay error when registering validator(s)"; - "num_registrations" => filtered_registration_data.len(), - "error" => ?e + num_registrations = filtered_registration_data.len(), + error = ?e, + "Relay error when registering validator(s)" ); // Forward the HTTP status code if we are able to, otherwise fall back // to a server error. @@ -3937,13 +3894,11 @@ pub fn serve( .and(validator_subscription_tx_filter) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |subscriptions: Vec, validator_subscription_tx: Sender, task_spawner: TaskSpawner, chain: Arc>, - log: Logger | { task_spawner.blocking_json_task(Priority::P0, move || { for subscription in subscriptions { @@ -3957,10 +3912,9 @@ pub fn serve( }; if let Err(e) = validator_subscription_tx.try_send(message) { warn!( - log, - "Unable to process sync subscriptions"; - "info" => "the host may be overloaded or resource-constrained", - "error" => ?e + info = "the host may be overloaded or resource-constrained", + error = ?e, + "Unable to process sync subscriptions" ); return Err(warp_utils::reject::custom_server_error( "unable to queue subscription, host may be overloaded or shutting down".to_string(), @@ -4016,6 +3970,117 @@ pub fn serve( }, ); + // POST lighthouse/finalize + let post_lighthouse_finalize = warp::path("lighthouse") + .and(warp::path("finalize")) + .and(warp::path::end()) + .and(warp_utils::json::json()) + .and(task_spawner_filter.clone()) + .and(chain_filter.clone()) + .then( + |request_data: api_types::ManualFinalizationRequestData, + task_spawner: TaskSpawner, + chain: Arc>| { + task_spawner.blocking_json_task(Priority::P0, move || { + let checkpoint = Checkpoint { + epoch: request_data.epoch, + root: request_data.block_root, + }; + + chain + .manually_finalize_state(request_data.state_root, checkpoint) + .map(|_| api_types::GenericResponse::from(request_data)) + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "Failed to finalize state due to error: {e:?}" + )) + }) + }) + }, + ); + + // POST lighthouse/compaction + let post_lighthouse_compaction = warp::path("lighthouse") + .and(warp::path("compaction")) + .and(warp::path::end()) + .and(task_spawner_filter.clone()) + .and(chain_filter.clone()) + .then( + |task_spawner: TaskSpawner, chain: Arc>| { + task_spawner.blocking_json_task(Priority::P0, move || { + chain.manually_compact_database(); + Ok(api_types::GenericResponse::from(String::from( + "Triggered manual compaction", + ))) + }) + }, + ); + + // POST lighthouse/add_peer + let post_lighthouse_add_peer = warp::path("lighthouse") + .and(warp::path("add_peer")) + .and(warp::path::end()) + .and(warp_utils::json::json()) + .and(task_spawner_filter.clone()) + .and(network_globals.clone()) + .and(network_tx_filter.clone()) + .then( + |request_data: api_types::AdminPeer, + task_spawner: TaskSpawner, + network_globals: Arc>, + network_tx: UnboundedSender>| { + task_spawner.blocking_json_task(Priority::P0, move || { + let enr = Enr::from_str(&request_data.enr).map_err(|e| { + warp_utils::reject::custom_bad_request(format!("invalid enr error {}", e)) + })?; + info!( + peer_id = %enr.peer_id(), + multiaddr = ?enr.multiaddr(), + "Adding trusted peer" + ); + network_globals.add_trusted_peer(enr.clone()); + + publish_network_message(&network_tx, NetworkMessage::ConnectTrustedPeer(enr))?; + + Ok(()) + }) + }, + ); + + // POST lighthouse/remove_peer + let post_lighthouse_remove_peer = warp::path("lighthouse") + .and(warp::path("remove_peer")) + .and(warp::path::end()) + .and(warp_utils::json::json()) + .and(task_spawner_filter.clone()) + .and(network_globals.clone()) + .and(network_tx_filter.clone()) + .then( + |request_data: api_types::AdminPeer, + task_spawner: TaskSpawner, + network_globals: Arc>, + network_tx: UnboundedSender>| { + task_spawner.blocking_json_task(Priority::P0, move || { + let enr = Enr::from_str(&request_data.enr).map_err(|e| { + warp_utils::reject::custom_bad_request(format!("invalid enr error {}", e)) + })?; + info!( + peer_id = %enr.peer_id(), + multiaddr = ?enr.multiaddr(), + "Removing trusted peer" + ); + network_globals.remove_trusted_peer(enr.clone()); + + publish_network_message( + &network_tx, + NetworkMessage::DisconnectTrustedPeer(enr), + )?; + + Ok(()) + }) + }, + ); + // POST lighthouse/liveness let post_lighthouse_liveness = warp::path("lighthouse") .and(warp::path("liveness")) @@ -4197,7 +4262,7 @@ pub fn serve( .peers .read() .peers() - .map(|(peer_id, peer_info)| eth2::lighthouse::Peer { + .map(|(peer_id, peer_info)| peer::Peer { peer_id: peer_id.to_string(), peer_info: peer_info.clone(), }) @@ -4217,15 +4282,14 @@ pub fn serve( |task_spawner: TaskSpawner, network_globals: Arc>| { task_spawner.blocking_json_task(Priority::P1, move || { - Ok(network_globals - .peers - .read() - .connected_peers() - .map(|(peer_id, peer_info)| eth2::lighthouse::Peer { + let mut peers = vec![]; + for (peer_id, peer_info) in network_globals.peers.read().connected_peers() { + peers.push(peer::Peer { peer_id: peer_id.to_string(), peer_info: peer_info.clone(), - }) - .collect::>()) + }); + } + Ok(peers) }) }, ); @@ -4430,10 +4494,9 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) - .then(|query, task_spawner: TaskSpawner, chain, log| { + .then(|query, task_spawner: TaskSpawner, chain| { task_spawner.blocking_json_task(Priority::P1, move || { - block_rewards::get_block_rewards(query, chain, log) + block_rewards::get_block_rewards(query, chain) }) }); @@ -4445,14 +4508,11 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) - .then( - |blocks, task_spawner: TaskSpawner, chain, log| { - task_spawner.blocking_json_task(Priority::P1, move || { - block_rewards::compute_block_rewards(blocks, chain, log) - }) - }, - ); + .then(|blocks, task_spawner: TaskSpawner, chain| { + task_spawner.blocking_json_task(Priority::P1, move || { + block_rewards::compute_block_rewards(blocks, chain) + }) + }); // GET lighthouse/analysis/attestation_performance/{index} let get_lighthouse_attestation_performance = warp::path("lighthouse") @@ -4630,7 +4690,9 @@ pub fn serve( match msg { Ok(data) => { // Serialize to json - match data.to_json_string() { + match serde_json::to_string(&data) + .map_err(|e| format!("{:?}", e)) + { // Send the json as a Server Side Event Ok(json) => Ok(Event::default().data(json)), Err(e) => { @@ -4673,6 +4735,8 @@ pub fn serve( .uor(get_beacon_state_committees) .uor(get_beacon_state_sync_committees) .uor(get_beacon_state_randao) + .uor(get_beacon_state_pending_deposits) + .uor(get_beacon_state_pending_partial_withdrawals) .uor(get_beacon_headers) .uor(get_beacon_headers_block_id) .uor(get_beacon_block) @@ -4723,22 +4787,10 @@ pub fn serve( .uor(get_lighthouse_database_info) .uor(get_lighthouse_block_rewards) .uor(get_lighthouse_attestation_performance) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_optimistic_update), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_finality_update), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_bootstrap), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_updates), - ) + .uor(get_beacon_light_client_optimistic_update) + .uor(get_beacon_light_client_finality_update) + .uor(get_beacon_light_client_bootstrap) + .uor(get_beacon_light_client_updates) .uor(get_lighthouse_block_packing_efficiency) .uor(get_lighthouse_merge_readiness) .uor(get_events) @@ -4786,11 +4838,14 @@ pub fn serve( .uor(post_lighthouse_block_rewards) .uor(post_lighthouse_ui_validator_metrics) .uor(post_lighthouse_ui_validator_info) + .uor(post_lighthouse_finalize) + .uor(post_lighthouse_compaction) + .uor(post_lighthouse_add_peer) + .uor(post_lighthouse_remove_peer) .recover(warp_utils::reject::handle_rejection), ), ) .recover(warp_utils::reject::handle_rejection) - .with(slog_logging(log.clone())) .with(prometheus_metrics()) // Add a `Server` header. .map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform())) @@ -4808,7 +4863,7 @@ pub fn serve( shutdown.await; })?; - info!(log, "HTTP API is being served over TLS";); + info!("HTTP API is being served over TLS"); (socket, Box::pin(server)) } @@ -4822,9 +4877,8 @@ pub fn serve( }; info!( - log, - "HTTP API started"; - "listen_address" => %http_server.0, + listen_address = %http_server.0, + "HTTP API started" ); Ok(http_server) diff --git a/beacon_node/http_api/src/peer.rs b/beacon_node/http_api/src/peer.rs new file mode 100644 index 0000000000..c9aea9d87c --- /dev/null +++ b/beacon_node/http_api/src/peer.rs @@ -0,0 +1,13 @@ +use lighthouse_network::PeerInfo; +use serde::Serialize; +use types::EthSpec; + +/// Information returned by `peers` and `connected_peers`. +#[derive(Debug, Clone, Serialize)] +#[serde(bound = "E: EthSpec")] +pub(crate) struct Peer { + /// The Peer's ID + pub peer_id: String, + /// The PeerInfo associated with the peer. + pub peer_info: PeerInfo, +} diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs index 0e24e8f175..22d6f0e7ae 100644 --- a/beacon_node/http_api/src/produce_block.rs +++ b/beacon_node/http_api/src/produce_block.rs @@ -147,7 +147,7 @@ pub async fn produce_blinded_block_v2( .produce_block_with_verification( randao_reveal, slot, - query.graffiti.map(Into::into), + query.graffiti, randao_verification, None, BlockProductionVersion::BlindedV2, @@ -178,7 +178,7 @@ pub async fn produce_block_v2( .produce_block_with_verification( randao_reveal, slot, - query.graffiti.map(Into::into), + query.graffiti, randao_verification, None, BlockProductionVersion::FullV2, diff --git a/beacon_node/http_api/src/proposer_duties.rs b/beacon_node/http_api/src/proposer_duties.rs index c4945df9d7..971571f487 100644 --- a/beacon_node/http_api/src/proposer_duties.rs +++ b/beacon_node/http_api/src/proposer_duties.rs @@ -7,9 +7,9 @@ use beacon_chain::{ }; use eth2::types::{self as api_types}; use safe_arith::SafeArith; -use slog::{debug, Logger}; use slot_clock::SlotClock; use std::cmp::Ordering; +use tracing::debug; use types::{Epoch, EthSpec, Hash256, Slot}; /// The struct that is returned to the requesting HTTP client. @@ -19,7 +19,6 @@ type ApiDuties = api_types::DutiesResponse>; pub fn proposer_duties( request_epoch: Epoch, chain: &BeaconChain, - log: &Logger, ) -> Result { let current_epoch = chain .slot_clock @@ -52,11 +51,7 @@ pub fn proposer_duties( if let Some(duties) = try_proposer_duties_from_cache(request_epoch, chain)? { Ok(duties) } else { - debug!( - log, - "Proposer cache miss"; - "request_epoch" => request_epoch, - ); + debug!(%request_epoch, "Proposer cache miss"); compute_and_cache_proposer_duties(request_epoch, chain) } } else if request_epoch diff --git a/beacon_node/http_api/src/publish_attestations.rs b/beacon_node/http_api/src/publish_attestations.rs index 10d13e09a5..cd5e912bdf 100644 --- a/beacon_node/http_api/src/publish_attestations.rs +++ b/beacon_node/http_api/src/publish_attestations.rs @@ -45,7 +45,6 @@ use eth2::types::Failure; use lighthouse_network::PubsubMessage; use network::NetworkMessage; use serde_json::Value; -use slog::{debug, error, warn, Logger}; use std::borrow::Cow; use std::sync::Arc; use std::time::Duration; @@ -53,6 +52,7 @@ use tokio::sync::{ mpsc::{Sender, UnboundedSender}, oneshot, }; +use tracing::{debug, error, warn}; use types::{Attestation, EthSpec, ForkName, SingleAttestation}; // Error variants are only used in `Debug` and considered `dead_code` by the compiler. @@ -80,14 +80,10 @@ enum PublishAttestationResult { pub fn deserialize_attestation_payload( payload: Value, fork_name: Option, - log: &Logger, ) -> Result, SingleAttestation>>, Error> { if fork_name.is_some_and(|fork_name| fork_name.electra_enabled()) || fork_name.is_none() { if fork_name.is_none() { - warn!( - log, - "No Consensus Version header specified."; - ); + warn!("No Consensus Version header specified."); } Ok(serde_json::from_value::>(payload) @@ -111,7 +107,6 @@ fn verify_and_publish_attestation( either_attestation: &Either, SingleAttestation>, seen_timestamp: Duration, network_tx: &UnboundedSender>, - log: &Logger, ) -> Result<(), Error> { let attestation = convert_to_attestation(chain, either_attestation)?; let verified_attestation = chain @@ -157,16 +152,14 @@ fn verify_and_publish_attestation( if let Err(e) = &fc_result { warn!( - log, - "Attestation invalid for fork choice"; - "err" => ?e, + err = ?e, + "Attestation invalid for fork choice" ); } if let Err(e) = &naive_aggregation_result { warn!( - log, - "Attestation invalid for aggregation"; - "err" => ?e + err = ?e, + "Attestation invalid for aggregation" ); } @@ -232,7 +225,6 @@ pub async fn publish_attestations( attestations: Vec, SingleAttestation>>, network_tx: UnboundedSender>, reprocess_send: Option>, - log: Logger, ) -> Result<(), warp::Rejection> { // Collect metadata about attestations which we'll use to report failures. We need to // move the `attestations` vec into the blocking task, so this small overhead is unavoidable. @@ -246,7 +238,6 @@ pub async fn publish_attestations( // Gossip validate and publish attestations that can be immediately processed. let seen_timestamp = timestamp_now(); - let inner_log = log.clone(); let mut prelim_results = task_spawner .blocking_task(Priority::P0, move || { Ok(attestations @@ -257,7 +248,6 @@ pub async fn publish_attestations( &attestation, seen_timestamp, &network_tx, - &inner_log, ) { Ok(()) => PublishAttestationResult::Success, Err(Error::Validation(AttestationError::UnknownHeadBlock { @@ -270,14 +260,12 @@ pub async fn publish_attestations( let (tx, rx) = oneshot::channel(); let reprocess_chain = chain.clone(); let reprocess_network_tx = network_tx.clone(); - let reprocess_log = inner_log.clone(); let reprocess_fn = move || { let result = verify_and_publish_attestation( &reprocess_chain, &attestation, seen_timestamp, &reprocess_network_tx, - &reprocess_log, ); // Ignore failure on the oneshot that reports the result. This // shouldn't happen unless some catastrophe befalls the waiting @@ -330,10 +318,9 @@ pub async fn publish_attestations( for (i, reprocess_result) in reprocess_indices.into_iter().zip(reprocess_results) { let Some(result_entry) = prelim_results.get_mut(i) else { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "prelim out of bounds", - "request_index" => i, + case = "prelim out of bounds", + request_index = i, + "Unreachable case in attestation publishing" ); continue; }; @@ -361,39 +348,35 @@ pub async fn publish_attestations( Some(PublishAttestationResult::Failure(e)) => { if let Some((slot, committee_index)) = attestation_metadata.get(index) { error!( - log, - "Failure verifying attestation for gossip"; - "error" => ?e, - "request_index" => index, - "committee_index" => committee_index, - "attestation_slot" => slot, + error = ?e, + request_index = index, + committee_index, + attestation_slot = %slot, + "Failure verifying attestation for gossip" ); failures.push(Failure::new(index, format!("{e:?}"))); } else { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "out of bounds", - "request_index" => index + case = "out of bounds", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "metadata logic error".into())); } } Some(PublishAttestationResult::Reprocessing(_)) => { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "reprocessing", - "request_index" => index + case = "reprocessing", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "reprocess logic error".into())); } None => { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "result is None", - "request_index" => index + case = "result is None", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "result logic error".into())); } @@ -402,9 +385,8 @@ pub async fn publish_attestations( if num_already_known > 0 { debug!( - log, - "Some unagg attestations already known"; - "count" => num_already_known + count = num_already_known, + "Some unagg attestations already known" ); } diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index 60d4b2f16e..a5cd94536d 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -18,13 +18,13 @@ use futures::TryFutureExt; use lighthouse_network::{NetworkGlobals, PubsubMessage}; use network::NetworkMessage; use rand::prelude::SliceRandom; -use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::{ AbstractExecPayload, BeaconBlockRef, BlobSidecar, BlobsList, BlockImportSource, @@ -80,12 +80,13 @@ pub async fn publish_block>( provenanced_block: ProvenancedBlock, chain: Arc>, network_tx: &UnboundedSender>, - log: Logger, validation_level: BroadcastValidation, duplicate_status_code: StatusCode, network_globals: Arc>, ) -> Result { let seen_timestamp = timestamp_now(); + let block_publishing_delay_for_testing = chain.config.block_publishing_delay; + let data_column_publishing_delay_for_testing = chain.config.data_column_publishing_delay; let (unverified_block, unverified_blobs, is_locally_built_block) = match provenanced_block { ProvenancedBlock::Local(block, blobs, _) => (block, blobs, true), @@ -97,12 +98,12 @@ pub async fn publish_block>( "builder" }; let block = unverified_block.inner_block(); - debug!(log, "Signed block received in HTTP API"; "slot" => block.slot()); + + debug!(slot = %block.slot(), "Signed block received in HTTP API"); /* actually publish a block */ let publish_block_p2p = move |block: Arc>, sender, - log, seen_timestamp| -> Result<(), BlockError> { let publish_timestamp = timestamp_now(); @@ -117,10 +118,9 @@ pub async fn publish_block>( ); info!( - log, - "Signed block published to network via HTTP API"; - "slot" => block.slot(), - "publish_delay_ms" => publish_delay.as_millis(), + slot = %block.slot(), + publish_delay_ms = publish_delay.as_millis(), + "Signed block published to network via HTTP API" ); crate::publish_pubsub_message(&sender, PubsubMessage::BeaconBlock(block.clone())) @@ -134,10 +134,11 @@ pub async fn publish_block>( let sender_clone = network_tx.clone(); let build_sidecar_task_handle = - spawn_build_data_sidecar_task(chain.clone(), block.clone(), unverified_blobs, log.clone())?; + spawn_build_data_sidecar_task(chain.clone(), block.clone(), unverified_blobs)?; // Gossip verify the block and blobs/data columns separately. - let gossip_verified_block_result = unverified_block.into_gossip_verified_block(&chain); + let gossip_verified_block_result = unverified_block + .into_gossip_verified_block(&chain, network_globals.custody_columns_count() as usize); let block_root = block_root.unwrap_or_else(|| { gossip_verified_block_result.as_ref().map_or_else( |_| block.canonical_root(), @@ -147,13 +148,15 @@ pub async fn publish_block>( let should_publish_block = gossip_verified_block_result.is_ok(); if BroadcastValidation::Gossip == validation_level && should_publish_block { - publish_block_p2p( - block.clone(), - sender_clone.clone(), - log.clone(), - seen_timestamp, - ) - .map_err(|_| warp_utils::reject::custom_server_error("unable to publish".into()))?; + if let Some(block_publishing_delay) = block_publishing_delay_for_testing { + debug!( + ?block_publishing_delay, + "Publishing block with artificial delay" + ); + tokio::time::sleep(block_publishing_delay).await; + } + publish_block_p2p(block.clone(), sender_clone.clone(), seen_timestamp) + .map_err(|_| warp_utils::reject::custom_server_error("unable to publish".into()))?; } let publish_fn_completed = Arc::new(AtomicBool::new(false)); @@ -165,15 +168,13 @@ pub async fn publish_block>( BroadcastValidation::Consensus => publish_block_p2p( block_to_publish.clone(), sender_clone.clone(), - log.clone(), seen_timestamp, )?, BroadcastValidation::ConsensusAndEquivocation => { - check_slashable(&chain, block_root, &block_to_publish, &log)?; + check_slashable(&chain, block_root, &block_to_publish)?; publish_block_p2p( block_to_publish.clone(), sender_clone.clone(), - log.clone(), seen_timestamp, )?; } @@ -196,17 +197,29 @@ pub async fn publish_block>( return if let BroadcastValidation::Gossip = validation_level { Err(warp_utils::reject::broadcast_without_import(msg)) } else { - error!( - log, - "Invalid blob provided to HTTP API"; - "reason" => &msg - ); + error!(reason = &msg, "Invalid blob provided to HTTP API"); Err(warp_utils::reject::custom_bad_request(msg)) }; } } if gossip_verified_columns.iter().map(Option::is_some).count() > 0 { + if let Some(data_column_publishing_delay) = data_column_publishing_delay_for_testing { + // Subtract block publishing delay if it is also used. + // Note: if `data_column_publishing_delay` is less than `block_publishing_delay`, it + // will still be delayed by `block_publishing_delay`. This could be solved with spawning + // async tasks but the limitation is minor and I believe it's probably not worth + // affecting the mainnet code path. + let block_publishing_delay = block_publishing_delay_for_testing.unwrap_or_default(); + let delay = data_column_publishing_delay.saturating_sub(block_publishing_delay); + if !delay.is_zero() { + debug!( + ?data_column_publishing_delay, + "Publishing data columns with artificial delay" + ); + tokio::time::sleep(delay).await; + } + } publish_column_sidecars(network_tx, &gossip_verified_columns, &chain).map_err(|_| { warp_utils::reject::custom_server_error("unable to publish data column sidecars".into()) })?; @@ -227,9 +240,8 @@ pub async fn publish_block>( Err(warp_utils::reject::broadcast_without_import(msg)) } else { error!( - log, - "Invalid data column during block publication"; - "reason" => &msg + reason = &msg, + "Invalid data column during block publication" ); Err(warp_utils::reject::custom_bad_request(msg)) }; @@ -253,7 +265,6 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } @@ -266,7 +277,6 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } else { @@ -286,10 +296,9 @@ pub async fn publish_block>( } Err(BlockError::DuplicateImportStatusUnknown(root)) => { debug!( - log, - "Block previously seen"; - "block_root" => ?root, - "slot" => block.slot(), + block_root = ?root, + slot = %block.slot(), + "Block previously seen" ); let import_result = Box::pin(chain.process_block( block_root, @@ -306,16 +315,14 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } Err(e) => { warn!( - log, - "Not publishing block - not gossip verified"; - "slot" => slot, - "error" => %e + %slot, + error = %e, + "Not publishing block - not gossip verified" ); Err(warp_utils::reject::custom_bad_request(e.to_string())) } @@ -338,7 +345,6 @@ fn spawn_build_data_sidecar_task( chain: Arc>, block: Arc>>, proofs_and_blobs: UnverifiedBlobs, - log: Logger, ) -> Result>, Rejection> { chain .clone() @@ -353,12 +359,12 @@ fn spawn_build_data_sidecar_task( if !peer_das_enabled { // Pre-PeerDAS: construct blob sidecars for the network. let gossip_verified_blobs = - build_gossip_verified_blobs(&chain, &block, blobs, kzg_proofs, &log)?; + build_gossip_verified_blobs(&chain, &block, blobs, kzg_proofs)?; Ok((gossip_verified_blobs, vec![])) } else { // Post PeerDAS: construct data columns. let gossip_verified_data_columns = - build_gossip_verified_data_columns(&chain, &block, blobs, &log)?; + build_gossip_verified_data_columns(&chain, &block, blobs)?; Ok((vec![], gossip_verified_data_columns)) } }, @@ -377,16 +383,14 @@ fn build_gossip_verified_data_columns( chain: &BeaconChain, block: &SignedBeaconBlock>, blobs: BlobsList, - log: &Logger, ) -> Result>>, Rejection> { let slot = block.slot(); let data_column_sidecars = build_blob_data_column_sidecars(chain, block, blobs).map_err(|e| { error!( - log, - "Invalid data column - not publishing block"; - "error" => ?e, - "slot" => slot + error = ?e, + %slot, + "Invalid data column - not publishing block" ); warp_utils::reject::custom_bad_request(format!("{e:?}")) })?; @@ -407,21 +411,19 @@ fn build_gossip_verified_data_columns( // or some of the other data columns if the block & data columns are only // partially published by the other publisher. debug!( - log, - "Data column for publication already known"; - "column_index" => column_index, - "slot" => slot, - "proposer" => proposer, + column_index, + %slot, + proposer, + "Data column for publication already known" ); Ok(None) } Err(e) => { error!( - log, - "Data column for publication is gossip-invalid"; - "column_index" => column_index, - "slot" => slot, - "error" => ?e, + column_index, + %slot, + error = ?e, + "Data column for publication is gossip-invalid" ); Err(warp_utils::reject::custom_bad_request(format!("{e:?}"))) } @@ -437,7 +439,6 @@ fn build_gossip_verified_blobs( block: &SignedBeaconBlock>, blobs: BlobsList, kzg_proofs: KzgProofs, - log: &Logger, ) -> Result>>, Rejection> { let slot = block.slot(); let gossip_verified_blobs = kzg_proofs @@ -452,11 +453,10 @@ fn build_gossip_verified_blobs( .map(Arc::new) .map_err(|e| { error!( - log, - "Invalid blob - not publishing block"; - "error" => ?e, - "blob_index" => i, - "slot" => slot, + error = ?e, + blob_index = i, + %slot, + "Invalid blob - not publishing block" ); warp_utils::reject::custom_bad_request(format!("{e:?}")) })?; @@ -472,21 +472,19 @@ fn build_gossip_verified_blobs( // or some of the other blobs if the block & blobs are only partially published // by the other publisher. debug!( - log, - "Blob for publication already known"; - "blob_index" => blob_sidecar.index, - "slot" => slot, - "proposer" => proposer, + blob_index = blob_sidecar.index, + %slot, + proposer, + "Blob for publication already known" ); Ok(None) } Err(e) => { error!( - log, - "Blob for publication is gossip-invalid"; - "blob_index" => blob_sidecar.index, - "slot" => slot, - "error" => ?e, + blob_index = blob_sidecar.index, + %slot, + error = ?e, + "Blob for publication is gossip-invalid" ); Err(warp_utils::reject::custom_bad_request(e.to_string())) } @@ -497,6 +495,15 @@ fn build_gossip_verified_blobs( Ok(gossip_verified_blobs) } +fn publish_blob_sidecars( + sender_clone: &UnboundedSender>, + blob: &GossipVerifiedBlob, +) -> Result<(), BlockError> { + let pubsub_message = PubsubMessage::BlobSidecar(Box::new((blob.index(), blob.clone_blob()))); + crate::publish_pubsub_message(sender_clone, pubsub_message) + .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) +} + fn publish_column_sidecars( sender_clone: &UnboundedSender>, data_column_sidecars: &[Option>], @@ -527,15 +534,6 @@ fn publish_column_sidecars( .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) } -fn publish_blob_sidecars( - sender_clone: &UnboundedSender>, - blob: &GossipVerifiedBlob, -) -> Result<(), BlockError> { - let pubsub_message = PubsubMessage::BlobSidecar(Box::new((blob.index(), blob.clone_blob()))); - crate::publish_pubsub_message(sender_clone, pubsub_message) - .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) -} - async fn post_block_import_logging_and_response( result: Result, validation_level: BroadcastValidation, @@ -543,7 +541,6 @@ async fn post_block_import_logging_and_response( is_locally_built_block: bool, seen_timestamp: Duration, chain: &Arc>, - log: &Logger, ) -> Result { match result { // The `DuplicateFullyImported` case here captures the case where the block finishes @@ -555,12 +552,11 @@ async fn post_block_import_logging_and_response( | Err(BlockError::DuplicateFullyImported(root)) => { let delay = get_block_delay_ms(seen_timestamp, block.message(), &chain.slot_clock); info!( - log, - "Valid block from HTTP API"; - "block_delay" => ?delay, - "root" => %root, - "proposer_index" => block.message().proposer_index(), - "slot" => block.slot(), + block_delay = ?delay, + root = %root, + proposer_index = block.message().proposer_index(), + slot = %block.slot(), + "Valid block from HTTP API" ); // Notify the validator monitor. @@ -579,7 +575,7 @@ async fn post_block_import_logging_and_response( // blocks built with builders we consider the broadcast time to be // when the blinded block is published to the builder. if is_locally_built_block { - late_block_logging(chain, seen_timestamp, block.message(), root, "local", log) + late_block_logging(chain, seen_timestamp, block.message(), root, "local") } Ok(warp::reply().into_response()) } @@ -588,11 +584,7 @@ async fn post_block_import_logging_and_response( if let BroadcastValidation::Gossip = validation_level { Err(warp_utils::reject::broadcast_without_import(msg)) } else { - error!( - log, - "Invalid block provided to HTTP API"; - "reason" => &msg - ); + error!(reason = &msg, "Invalid block provided to HTTP API"); Err(warp_utils::reject::custom_bad_request(msg)) } } @@ -609,9 +601,8 @@ async fn post_block_import_logging_and_response( Err(warp_utils::reject::broadcast_without_import(format!("{e}"))) } else { error!( - log, - "Invalid block provided to HTTP API"; - "reason" => ?e, + reason = ?e, + "Invalid block provided to HTTP API" ); Err(warp_utils::reject::custom_bad_request(format!( "Invalid block: {e}" @@ -627,20 +618,17 @@ pub async fn publish_blinded_block( blinded_block: Arc>, chain: Arc>, network_tx: &UnboundedSender>, - log: Logger, validation_level: BroadcastValidation, duplicate_status_code: StatusCode, network_globals: Arc>, ) -> Result { let block_root = blinded_block.canonical_root(); - let full_block = - reconstruct_block(chain.clone(), block_root, blinded_block, log.clone()).await?; + let full_block = reconstruct_block(chain.clone(), block_root, blinded_block).await?; publish_block::( Some(block_root), full_block, chain, network_tx, - log, validation_level, duplicate_status_code, network_globals, @@ -655,7 +643,6 @@ pub async fn reconstruct_block( chain: Arc>, block_root: Hash256, block: Arc>, - log: Logger, ) -> Result>>, Rejection> { let full_payload_opt = if let Ok(payload_header) = block.message().body().execution_payload() { let el = chain.execution_layer.as_ref().ok_or_else(|| { @@ -679,7 +666,7 @@ pub async fn reconstruct_block( } else if let Some(cached_payload) = el.get_payload_by_root(&payload_header.tree_hash_root()) { - info!(log, "Reconstructing a full block using a local payload"; "block_hash" => ?cached_payload.block_hash()); + info!(block_hash = ?cached_payload.block_hash(), "Reconstructing a full block using a local payload"); ProvenancedPayload::Local(cached_payload) // Otherwise, this means we are attempting a blind block proposal. } else { @@ -694,7 +681,6 @@ pub async fn reconstruct_block( block.message(), block_root, "builder", - &log, ); let full_payload = el @@ -706,7 +692,7 @@ pub async fn reconstruct_block( e )) })?; - info!(log, "Successfully published a block to the builder network"; "block_hash" => ?full_payload.block_hash()); + info!(block_hash = ?full_payload.block_hash(), "Successfully published a block to the builder network"); ProvenancedPayload::Builder(full_payload) }; @@ -748,7 +734,6 @@ fn late_block_logging>( block: BeaconBlockRef, root: Hash256, provenance: &str, - log: &Logger, ) { let delay = get_block_delay_ms(seen_timestamp, block, &chain.slot_clock); @@ -767,23 +752,21 @@ fn late_block_logging>( let delayed_threshold = too_late_threshold / 2; if delay >= too_late_threshold { error!( - log, - "Block was broadcast too late"; - "msg" => "system may be overloaded, block likely to be orphaned", - "provenance" => provenance, - "delay_ms" => delay.as_millis(), - "slot" => block.slot(), - "root" => ?root, + msg = "system may be overloaded, block likely to be orphaned", + provenance, + delay_ms = delay.as_millis(), + slot = %block.slot(), + ?root, + "Block was broadcast too late" ) } else if delay >= delayed_threshold { error!( - log, - "Block broadcast was delayed"; - "msg" => "system may be overloaded, block may be orphaned", - "provenance" => provenance, - "delay_ms" => delay.as_millis(), - "slot" => block.slot(), - "root" => ?root, + msg = "system may be overloaded, block may be orphaned", + provenance, + delay_ms = delay.as_millis(), + slot = %block.slot(), + ?root, + "Block broadcast was delayed" ) } } @@ -793,7 +776,6 @@ fn check_slashable( chain_clone: &BeaconChain, block_root: Hash256, block_clone: &SignedBeaconBlock>, - log_clone: &Logger, ) -> Result<(), BlockError> { let slashable_cache = chain_clone.observed_slashable.read(); if slashable_cache @@ -805,9 +787,8 @@ fn check_slashable( .map_err(|e| BlockError::BeaconChainError(e.into()))? { warn!( - log_clone, - "Not publishing equivocating block"; - "slot" => block_clone.slot() + slot = %block_clone.slot(), + "Not publishing equivocating block" ); return Err(BlockError::Slashable); } diff --git a/beacon_node/http_api/src/standard_block_rewards.rs b/beacon_node/http_api/src/standard_block_rewards.rs index 372a2765da..2f78649d78 100644 --- a/beacon_node/http_api/src/standard_block_rewards.rs +++ b/beacon_node/http_api/src/standard_block_rewards.rs @@ -2,7 +2,7 @@ use crate::sync_committee_rewards::get_state_before_applying_block; use crate::BlockId; use crate::ExecutionOptimistic; use beacon_chain::{BeaconChain, BeaconChainTypes}; -use eth2::lighthouse::StandardBlockReward; +use eth2::types::StandardBlockReward; use std::sync::Arc; use warp_utils::reject::unhandled_error; /// The difference between block_rewards and beacon_block_rewards is the later returns block diff --git a/beacon_node/http_api/src/state_id.rs b/beacon_node/http_api/src/state_id.rs index 353390cdad..a9f66de467 100644 --- a/beacon_node/http_api/src/state_id.rs +++ b/beacon_node/http_api/src/state_id.rs @@ -189,8 +189,10 @@ impl StateId { _ => (self.root(chain)?, None), }; + // This branch is reached from the HTTP API. We assume the user wants + // to cache states so that future calls are faster. let state = chain - .get_state(&state_root, slot_opt) + .get_state(&state_root, slot_opt, true) .map_err(warp_utils::reject::unhandled_error) .and_then(|opt| { opt.ok_or_else(|| { diff --git a/beacon_node/http_api/src/sync_committee_rewards.rs b/beacon_node/http_api/src/sync_committee_rewards.rs index ec63372406..9bc1f6ead4 100644 --- a/beacon_node/http_api/src/sync_committee_rewards.rs +++ b/beacon_node/http_api/src/sync_committee_rewards.rs @@ -1,10 +1,9 @@ use crate::{BlockId, ExecutionOptimistic}; use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes}; -use eth2::lighthouse::SyncCommitteeReward; -use eth2::types::ValidatorId; -use slog::{debug, Logger}; +use eth2::types::{SyncCommitteeReward, ValidatorId}; use state_processing::BlockReplayer; use std::sync::Arc; +use tracing::debug; use types::{BeaconState, SignedBlindedBeaconBlock}; use warp_utils::reject::{custom_not_found, unhandled_error}; @@ -12,7 +11,6 @@ pub fn compute_sync_committee_rewards( chain: Arc>, block_id: BlockId, validators: Vec, - log: Logger, ) -> Result<(Option>, ExecutionOptimistic, bool), warp::Rejection> { let (block, execution_optimistic, finalized) = block_id.blinded_block(&chain)?; @@ -23,7 +21,7 @@ pub fn compute_sync_committee_rewards( .map_err(unhandled_error)?; let data = if reward_payload.is_empty() { - debug!(log, "compute_sync_committee_rewards returned empty"); + debug!("compute_sync_committee_rewards returned empty"); None } else if validators.is_empty() { Some(reward_payload) @@ -58,8 +56,10 @@ pub fn get_state_before_applying_block( }) .map_err(|e| custom_not_found(format!("Parent block is not available! {:?}", e)))?; + // We are about to apply a new block to the chain. It's parent state + // is a useful/recent state, we elect to cache it. let parent_state = chain - .get_state(&parent_block.state_root(), Some(parent_block.slot())) + .get_state(&parent_block.state_root(), Some(parent_block.slot()), true) .and_then(|maybe_state| { maybe_state .ok_or_else(|| BeaconChainError::MissingBeaconState(parent_block.state_root())) diff --git a/beacon_node/http_api/src/sync_committees.rs b/beacon_node/http_api/src/sync_committees.rs index da9f9b7a06..9ca1a2401a 100644 --- a/beacon_node/http_api/src/sync_committees.rs +++ b/beacon_node/http_api/src/sync_committees.rs @@ -11,11 +11,11 @@ use beacon_chain::{ use eth2::types::{self as api_types}; use lighthouse_network::PubsubMessage; use network::NetworkMessage; -use slog::{debug, error, warn, Logger}; use slot_clock::SlotClock; use std::cmp::max; use std::collections::HashMap; use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, error, warn}; use types::{ slot_data::SlotData, BeaconStateError, Epoch, EthSpec, SignedContributionAndProof, SyncCommitteeMessage, SyncDuty, SyncSubnetId, @@ -178,7 +178,6 @@ pub fn process_sync_committee_signatures( sync_committee_signatures: Vec, network_tx: UnboundedSender>, chain: &BeaconChain, - log: Logger, ) -> Result<(), warp::reject::Rejection> { let mut failures = vec![]; @@ -192,10 +191,9 @@ pub fn process_sync_committee_signatures( Ok(positions) => positions, Err(e) => { error!( - log, - "Unable to compute subnet positions for sync message"; - "error" => ?e, - "slot" => sync_committee_signature.slot, + error = ?e, + slot = %sync_committee_signature.slot, + "Unable to compute subnet positions for sync message" ); failures.push(api_types::Failure::new(i, format!("Verification: {:?}", e))); continue; @@ -248,22 +246,20 @@ pub fn process_sync_committee_signatures( new_root, }) => { debug!( - log, - "Ignoring already-known sync message"; - "new_root" => ?new_root, - "prev_root" => ?prev_root, - "slot" => slot, - "validator_index" => validator_index, + ?new_root, + ?prev_root, + %slot, + validator_index, + "Ignoring already-known sync message" ); } Err(e) => { error!( - log, - "Failure verifying sync committee signature for gossip"; - "error" => ?e, - "request_index" => i, - "slot" => sync_committee_signature.slot, - "validator_index" => sync_committee_signature.validator_index, + error = ?e, + request_index = i, + slot = %sync_committee_signature.slot, + validator_index = sync_committee_signature.validator_index, + "Failure verifying sync committee signature for gossip" ); failures.push(api_types::Failure::new(i, format!("Verification: {:?}", e))); } @@ -273,11 +269,10 @@ pub fn process_sync_committee_signatures( if let Some(verified) = verified_for_pool { if let Err(e) = chain.add_to_naive_sync_aggregation_pool(verified) { error!( - log, - "Unable to add sync committee signature to pool"; - "error" => ?e, - "slot" => sync_committee_signature.slot, - "validator_index" => sync_committee_signature.validator_index, + error = ?e, + slot = %sync_committee_signature.slot, + validator_index = sync_committee_signature.validator_index, + "Unable to add sync committee signature to pool" ); } } @@ -312,7 +307,6 @@ pub fn process_signed_contribution_and_proofs( signed_contribution_and_proofs: Vec>, network_tx: UnboundedSender>, chain: &BeaconChain, - log: Logger, ) -> Result<(), warp::reject::Rejection> { let mut verified_contributions = Vec::with_capacity(signed_contribution_and_proofs.len()); let mut failures = vec![]; @@ -362,13 +356,12 @@ pub fn process_signed_contribution_and_proofs( Err(SyncVerificationError::AggregatorAlreadyKnown(_)) => continue, Err(e) => { error!( - log, - "Failure verifying signed contribution and proof"; - "error" => ?e, - "request_index" => index, - "aggregator_index" => aggregator_index, - "subcommittee_index" => subcommittee_index, - "contribution_slot" => contribution_slot, + error = ?e, + request_index = index, + aggregator_index = aggregator_index, + subcommittee_index = subcommittee_index, + contribution_slot = %contribution_slot, + "Failure verifying signed contribution and proof" ); failures.push(api_types::Failure::new( index, @@ -382,10 +375,9 @@ pub fn process_signed_contribution_and_proofs( for (index, verified_contribution) in verified_contributions { if let Err(e) = chain.add_contribution_to_block_inclusion_pool(verified_contribution) { warn!( - log, - "Could not add verified sync contribution to the inclusion pool"; - "error" => ?e, - "request_index" => index, + error = ?e, + request_index = index, + "Could not add verified sync contribution to the inclusion pool" ); failures.push(api_types::Failure::new(index, format!("Op pool: {:?}", e))); } diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index fbc92a45cc..f78a361dad 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -19,10 +19,8 @@ use lighthouse_network::{ types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield, SyncState}, ConnectedPoint, Enr, NetworkConfig, NetworkGlobals, PeerId, PeerManager, }; -use logging::test_logger; use network::{NetworkReceivers, NetworkSenders}; use sensitive_url::SensitiveUrl; -use slog::Logger; use std::future::Future; use std::net::SocketAddr; use std::sync::Arc; @@ -75,7 +73,6 @@ impl InteractiveTester { ) -> Self { let mut harness_builder = BeaconChainHarness::builder(E::default()) .spec_or_default(spec.map(Arc::new)) - .logger(test_logger()) .mock_execution_layer(); harness_builder = if let Some(initializer) = initializer { @@ -102,13 +99,7 @@ impl InteractiveTester { listening_socket, network_rx, .. - } = create_api_server_with_config( - harness.chain.clone(), - config, - &harness.runtime, - harness.logger().clone(), - ) - .await; + } = create_api_server_with_config(harness.chain.clone(), config, &harness.runtime).await; tokio::spawn(server); @@ -134,16 +125,14 @@ impl InteractiveTester { pub async fn create_api_server( chain: Arc>, test_runtime: &TestRuntime, - log: Logger, ) -> ApiServer> { - create_api_server_with_config(chain, Config::default(), test_runtime, log).await + create_api_server_with_config(chain, Config::default(), test_runtime).await } pub async fn create_api_server_with_config( chain: Arc>, http_config: Config, test_runtime: &TestRuntime, - log: Logger, ) -> ApiServer> { // Use port 0 to allocate a new unused port. let port = 0; @@ -174,14 +163,13 @@ pub async fn create_api_server_with_config( meta_data, vec![], false, - &log, network_config, chain.spec.clone(), )); // Only a peer manager can add peers, so we create a dummy manager. let config = lighthouse_network::peer_manager::config::Config::default(); - let mut pm = PeerManager::new(config, network_globals.clone(), &log).unwrap(); + let mut pm = PeerManager::new(config, network_globals.clone()).unwrap(); // add a peer let peer_id = PeerId::random(); @@ -200,8 +188,7 @@ pub async fn create_api_server_with_config( })); *network_globals.sync_state.write() = SyncState::Synced; - let eth1_service = - eth1::Service::new(eth1::Config::default(), log.clone(), chain.spec.clone()).unwrap(); + let eth1_service = eth1::Service::new(eth1::Config::default(), chain.spec.clone()).unwrap(); let beacon_processor_config = BeaconProcessorConfig { // The number of workers must be greater than one. Tests which use the @@ -225,7 +212,6 @@ pub async fn create_api_server_with_config( executor: test_runtime.task_executor.clone(), current_workers: 0, config: beacon_processor_config, - log: log.clone(), } .spawn_manager( beacon_processor_rx, @@ -249,7 +235,6 @@ pub async fn create_api_server_with_config( enabled: true, listen_port: port, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), - enable_light_client_server: true, ..http_config }, chain: Some(chain), @@ -259,7 +244,6 @@ pub async fn create_api_server_with_config( beacon_processor_reprocess_send: Some(reprocess_send), eth1_service: Some(eth1_service), sse_logging_components: None, - log, }); let (listening_socket, server) = diff --git a/beacon_node/http_api/src/validators.rs b/beacon_node/http_api/src/validators.rs index 93e63953ef..f3d78e6fcd 100644 --- a/beacon_node/http_api/src/validators.rs +++ b/beacon_node/http_api/src/validators.rs @@ -29,7 +29,7 @@ pub fn get_beacon_state_validators( .enumerate() // filter by validator id(s) if provided .filter(|(index, (validator, _))| { - ids_filter_set.as_ref().map_or(true, |ids_set| { + ids_filter_set.as_ref().is_none_or(|ids_set| { ids_set.contains(&ValidatorId::PublicKey(validator.pubkey)) || ids_set.contains(&ValidatorId::Index(*index as u64)) }) @@ -42,7 +42,7 @@ pub fn get_beacon_state_validators( far_future_epoch, ); - let status_matches = query_statuses.as_ref().map_or(true, |statuses| { + let status_matches = query_statuses.as_ref().is_none_or(|statuses| { statuses.contains(&status) || statuses.contains(&status.superstatus()) }); @@ -92,7 +92,7 @@ pub fn get_beacon_state_validator_balances( .enumerate() // filter by validator id(s) if provided .filter(|(index, (validator, _))| { - ids_filter_set.as_ref().map_or(true, |ids_set| { + ids_filter_set.as_ref().is_none_or(|ids_set| { ids_set.contains(&ValidatorId::PublicKey(validator.pubkey)) || ids_set.contains(&ValidatorId::Index(*index as u64)) }) diff --git a/beacon_node/http_api/tests/broadcast_validation_tests.rs b/beacon_node/http_api/tests/broadcast_validation_tests.rs index 1baa71699c..cd590580be 100644 --- a/beacon_node/http_api/tests/broadcast_validation_tests.rs +++ b/beacon_node/http_api/tests/broadcast_validation_tests.rs @@ -39,6 +39,9 @@ type E = MainnetEthSpec; * */ +// Default custody group count for tests +const CGC: usize = 8; + /// This test checks that a block that is **invalid** from a gossip perspective gets rejected when using `broadcast_validation=gossip`. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] pub async fn gossip_invalid() { @@ -331,7 +334,6 @@ pub async fn consensus_partial_pass_only_consensus() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -365,9 +367,9 @@ pub async fn consensus_partial_pass_only_consensus() { ); assert_ne!(block_a.state_root(), block_b.state_root()); - let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain); + let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain, CGC); assert!(gossip_block_b.is_ok()); - let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain); + let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain, CGC); assert!(gossip_block_a.is_err()); /* submit `block_b` which should induce equivocation */ @@ -379,7 +381,6 @@ pub async fn consensus_partial_pass_only_consensus() { ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b), tester.harness.chain.clone(), &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, @@ -624,7 +625,6 @@ pub async fn equivocation_consensus_late_equivocation() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -657,10 +657,10 @@ pub async fn equivocation_consensus_late_equivocation() { ); assert_ne!(block_a.state_root(), block_b.state_root()); - let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain); + let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain, CGC); assert!(gossip_block_b.is_ok()); - let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain); + let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain, CGC); assert!(gossip_block_a.is_err()); let channel = tokio::sync::mpsc::unbounded_channel(); @@ -671,7 +671,6 @@ pub async fn equivocation_consensus_late_equivocation() { ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b), tester.harness.chain, &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, @@ -1236,7 +1235,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -1276,7 +1274,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { tester.harness.chain.clone(), block_a.canonical_root(), Arc::new(block_a), - test_logger.clone(), ) .await .unwrap(); @@ -1284,7 +1281,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { tester.harness.chain.clone(), block_b.canonical_root(), block_b.clone(), - test_logger.clone(), ) .await .unwrap(); @@ -1298,9 +1294,9 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { ProvenancedBlock::Builder(b, _, _) => b, }; - let gossip_block_b = GossipVerifiedBlock::new(inner_block_b, &tester.harness.chain); + let gossip_block_b = GossipVerifiedBlock::new(inner_block_b, &tester.harness.chain, CGC); assert!(gossip_block_b.is_ok()); - let gossip_block_a = GossipVerifiedBlock::new(inner_block_a, &tester.harness.chain); + let gossip_block_a = GossipVerifiedBlock::new(inner_block_a, &tester.harness.chain, CGC); assert!(gossip_block_a.is_err()); let channel = tokio::sync::mpsc::unbounded_channel(); @@ -1310,7 +1306,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { block_b, tester.harness.chain, &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, @@ -1403,7 +1398,7 @@ pub async fn block_seen_on_gossip_without_blobs() { // Simulate the block being seen on gossip. block .clone() - .into_gossip_verified_block(&tester.harness.chain) + .into_gossip_verified_block(&tester.harness.chain, CGC) .unwrap(); // It should not yet be added to fork choice because blobs have not been seen. @@ -1472,7 +1467,7 @@ pub async fn block_seen_on_gossip_with_some_blobs() { // Simulate the block being seen on gossip. block .clone() - .into_gossip_verified_block(&tester.harness.chain) + .into_gossip_verified_block(&tester.harness.chain, CGC) .unwrap(); // Simulate some of the blobs being seen on gossip. diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index bc3159e074..6d407d2742 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -26,7 +26,6 @@ use http_api::{ BlockId, StateId, }; use lighthouse_network::{types::SyncState, Enr, EnrExt, PeerId}; -use logging::test_logger; use network::NetworkReceivers; use proto_array::ExecutionStatus; use sensitive_url::SensitiveUrl; @@ -135,7 +134,6 @@ impl ApiTester { reconstruct_historic_states: config.retain_historic_states, ..ChainConfig::default() }) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .deterministic_withdrawal_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() @@ -277,8 +275,6 @@ impl ApiTester { "precondition: justification" ); - let log = test_logger(); - let ApiServer { ctx, server, @@ -286,7 +282,7 @@ impl ApiTester { network_rx, local_enr, external_peer_id, - } = create_api_server(chain.clone(), &harness.runtime, log).await; + } = create_api_server(chain.clone(), &harness.runtime).await; harness.runtime.task_executor.spawn(server, "api_server"); @@ -375,7 +371,6 @@ impl ApiTester { let bls_to_execution_change = harness.make_bls_to_execution_change(4, Address::zero()); let chain = harness.chain.clone(); - let log = test_logger(); let ApiServer { ctx, @@ -384,7 +379,7 @@ impl ApiTester { network_rx, local_enr, external_peer_id, - } = create_api_server(chain.clone(), &harness.runtime, log).await; + } = create_api_server(chain.clone(), &harness.runtime).await; harness.runtime.task_executor.spawn(server, "api_server"); @@ -1192,6 +1187,60 @@ impl ApiTester { self } + pub async fn test_beacon_states_pending_deposits(self) -> Self { + for state_id in self.interesting_state_ids() { + let mut state_opt = state_id + .state(&self.chain) + .ok() + .map(|(state, _execution_optimistic, _finalized)| state); + + let result = self + .client + .get_beacon_states_pending_deposits(state_id.0) + .await + .unwrap() + .map(|res| res.data); + + if result.is_none() && state_opt.is_none() { + continue; + } + + let state = state_opt.as_mut().expect("result should be none"); + let expected = state.pending_deposits().unwrap(); + + assert_eq!(result.unwrap(), expected.to_vec()); + } + + self + } + + pub async fn test_beacon_states_pending_partial_withdrawals(self) -> Self { + for state_id in self.interesting_state_ids() { + let mut state_opt = state_id + .state(&self.chain) + .ok() + .map(|(state, _execution_optimistic, _finalized)| state); + + let result = self + .client + .get_beacon_states_pending_partial_withdrawals(state_id.0) + .await + .unwrap() + .map(|res| res.data); + + if result.is_none() && state_opt.is_none() { + continue; + } + + let state = state_opt.as_mut().expect("result should be none"); + let expected = state.pending_partial_withdrawals().unwrap(); + + assert_eq!(result.unwrap(), expected.to_vec()); + } + + self + } + pub async fn test_beacon_headers_all_slots(self) -> Self { for slot in 0..CHAIN_LENGTH { let slot = Slot::from(slot); @@ -2450,8 +2499,8 @@ impl ApiTester { }; let state_match = - states.map_or(true, |states| states.contains(&PeerState::Connected)); - let dir_match = dirs.map_or(true, |dirs| dirs.contains(&PeerDirection::Inbound)); + states.is_none_or(|states| states.contains(&PeerState::Connected)); + let dir_match = dirs.is_none_or(|dirs| dirs.contains(&PeerDirection::Inbound)); let mut expected_peers = Vec::new(); if state_match && dir_match { @@ -3555,44 +3604,48 @@ impl ApiTester { } #[allow(clippy::await_holding_lock)] // This is a test, so it should be fine. - pub async fn test_get_validator_aggregate_attestation(self) -> Self { - if self + pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self { + let attestation = self .chain - .spec - .fork_name_at_slot::(self.chain.slot().unwrap()) - .electra_enabled() - { - for attestation in self.chain.naive_aggregation_pool.read().iter() { - let result = self - .client - .get_validator_aggregate_attestation_v2( - attestation.data().slot, - attestation.data().tree_hash_root(), - attestation.committee_index().expect("committee index"), - ) - .await - .unwrap() - .unwrap() - .data; - let expected = attestation; + .head_beacon_block() + .message() + .body() + .attestations() + .next() + .unwrap() + .clone_as_attestation(); + let result = self + .client + .get_validator_aggregate_attestation_v1( + attestation.data().slot, + attestation.data().tree_hash_root(), + ) + .await + .unwrap() + .unwrap() + .data; + let expected = attestation; - assert_eq!(&result, expected); - } - } else { - let attestation = self - .chain - .head_beacon_block() - .message() - .body() - .attestations() - .next() - .unwrap() - .clone_as_attestation(); + assert_eq!(result, expected); + + self + } + + pub async fn test_get_validator_aggregate_attestation_v2(self) -> Self { + let attestations = self + .chain + .naive_aggregation_pool + .read() + .iter() + .cloned() + .collect::>(); + for attestation in attestations { let result = self .client - .get_validator_aggregate_attestation_v1( + .get_validator_aggregate_attestation_v2( attestation.data().slot, attestation.data().tree_hash_root(), + attestation.committee_index().expect("committee index"), ) .await .unwrap() @@ -3602,7 +3655,6 @@ impl ApiTester { assert_eq!(result, expected); } - self } @@ -5688,19 +5740,6 @@ impl ApiTester { self } - pub async fn test_get_lighthouse_database_info(self) -> Self { - let info = self.client.get_lighthouse_database_info().await.unwrap(); - - assert_eq!(info.anchor, self.chain.store.get_anchor_info()); - assert_eq!(info.split, self.chain.store.get_split_info()); - assert_eq!( - info.schema_version, - store::metadata::CURRENT_SCHEMA_VERSION.as_u64() - ); - - self - } - pub async fn test_post_lighthouse_database_reconstruct(self) -> Self { let response = self .client @@ -5711,6 +5750,27 @@ impl ApiTester { self } + pub async fn test_post_lighthouse_add_remove_peer(self) -> Self { + let trusted_peers = self.ctx.network_globals.as_ref().unwrap().trusted_peers(); + // Check that there aren't any trusted peers on startup + assert!(trusted_peers.is_empty()); + let enr = AdminPeer {enr: "enr:-QESuEDpVVjo8dmDuneRhLnXdIGY3e9NQiaG4sJR3GS-VMQCQDsmBYoQhJRaPeZzPlTsZj2F8v-iV4lKJEYIRIyztqexHodhdHRuZXRziAwAAAAAAAAAhmNsaWVudNiKTGlnaHRob3VzZYw3LjAuMC1iZXRhLjSEZXRoMpDS8Zl_YAAJEAAIAAAAAAAAgmlkgnY0gmlwhIe11XmDaXA2kCoBBPkAOitZAAAAAAAAAAKEcXVpY4IjKYVxdWljNoIjg4lzZWNwMjU2azGhA43ihEr9BUVVnIHIfFqBR3Izs4YRHHPsTqIbUgEb3Hc8iHN5bmNuZXRzD4N0Y3CCIyiEdGNwNoIjgoN1ZHCCIyiEdWRwNoIjgg".to_string()}; + self.client + .post_lighthouse_add_peer(enr.clone()) + .await + .unwrap(); + let trusted_peers = self.ctx.network_globals.as_ref().unwrap().trusted_peers(); + // Should have 1 trusted peer + assert_eq!(trusted_peers.len(), 1); + + self.client.post_lighthouse_remove_peer(enr).await.unwrap(); + let trusted_peers = self.ctx.network_globals.as_ref().unwrap().trusted_peers(); + // Should be empty after removing + assert!(trusted_peers.is_empty()); + + self + } + pub async fn test_post_lighthouse_liveness(self) -> Self { let epoch = self.chain.epoch().unwrap(); let head_state = self.chain.head_beacon_state_cloned(); @@ -6313,6 +6373,22 @@ async fn beacon_get_state_info() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn beacon_get_state_info_electra() { + let mut config = ApiTesterConfig::default(); + config.spec.altair_fork_epoch = Some(Epoch::new(0)); + config.spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + config.spec.capella_fork_epoch = Some(Epoch::new(0)); + config.spec.deneb_fork_epoch = Some(Epoch::new(0)); + config.spec.electra_fork_epoch = Some(Epoch::new(0)); + ApiTester::new_from_config(config) + .await + .test_beacon_states_pending_deposits() + .await + .test_beacon_states_pending_partial_withdrawals() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn beacon_get_blocks() { ApiTester::new() @@ -6775,19 +6851,36 @@ async fn get_validator_attestation_data_with_skip_slots() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_validator_aggregate_attestation() { +async fn get_validator_aggregate_attestation_v1() { ApiTester::new() .await - .test_get_validator_aggregate_attestation() + .test_get_validator_aggregate_attestation_v1() .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_validator_aggregate_attestation_with_skip_slots() { +async fn get_validator_aggregate_attestation_v2() { + ApiTester::new() + .await + .test_get_validator_aggregate_attestation_v2() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_validator_aggregate_attestation_with_skip_slots_v1() { ApiTester::new() .await .skip_slots(E::slots_per_epoch() * 2) - .test_get_validator_aggregate_attestation() + .test_get_validator_aggregate_attestation_v1() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_validator_aggregate_attestation_with_skip_slots_v2() { + ApiTester::new() + .await + .skip_slots(E::slots_per_epoch() * 2) + .test_get_validator_aggregate_attestation_v2() .await; } @@ -7239,11 +7332,11 @@ async fn lighthouse_endpoints() { .await .test_get_lighthouse_staking() .await - .test_get_lighthouse_database_info() - .await .test_post_lighthouse_database_reconstruct() .await .test_post_lighthouse_liveness() + .await + .test_post_lighthouse_add_remove_peer() .await; } diff --git a/beacon_node/http_metrics/Cargo.toml b/beacon_node/http_metrics/Cargo.toml index 9ad073439d..e12053ac43 100644 --- a/beacon_node/http_metrics/Cargo.toml +++ b/beacon_node/http_metrics/Cargo.toml @@ -10,12 +10,13 @@ beacon_chain = { workspace = true } health_metrics = { workspace = true } lighthouse_network = { workspace = true } lighthouse_version = { workspace = true } +logging = { workspace = true } malloc_utils = { workspace = true } metrics = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } store = { workspace = true } +tracing = { workspace = true } warp = { workspace = true } warp_utils = { workspace = true } diff --git a/beacon_node/http_metrics/src/lib.rs b/beacon_node/http_metrics/src/lib.rs index 2895506c3b..6cbb485d71 100644 --- a/beacon_node/http_metrics/src/lib.rs +++ b/beacon_node/http_metrics/src/lib.rs @@ -6,12 +6,13 @@ mod metrics; use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::prometheus_client::registry::Registry; use lighthouse_version::version_with_platform; +use logging::crit; use serde::{Deserialize, Serialize}; -use slog::{crit, info, Logger}; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; +use tracing::info; use warp::{http::Response, Filter}; #[derive(Debug)] @@ -41,7 +42,6 @@ pub struct Context { pub db_path: Option, pub freezer_db_path: Option, pub gossipsub_registry: Option>, - pub log: Logger, } /// Configuration for the HTTP server. @@ -86,7 +86,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -103,7 +102,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -144,9 +143,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/beacon_node/http_metrics/tests/tests.rs b/beacon_node/http_metrics/tests/tests.rs index d903e233fb..2de2fd96f8 100644 --- a/beacon_node/http_metrics/tests/tests.rs +++ b/beacon_node/http_metrics/tests/tests.rs @@ -1,6 +1,6 @@ use beacon_chain::test_utils::EphemeralHarnessType; use http_metrics::Config; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use reqwest::header::HeaderValue; use reqwest::StatusCode; use std::net::{IpAddr, Ipv4Addr}; @@ -12,9 +12,8 @@ type Context = http_metrics::Context>; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn returns_200_ok() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let context = Arc::new(Context { config: Config { enabled: true, @@ -27,7 +26,6 @@ async fn returns_200_ok() { db_path: None, freezer_db_path: None, gossipsub_registry: None, - log, }); let ctx = context.clone(); diff --git a/beacon_node/lighthouse_network/Cargo.toml b/beacon_node/lighthouse_network/Cargo.toml index b16ccc2a8c..4f1825af20 100644 --- a/beacon_node/lighthouse_network/Cargo.toml +++ b/beacon_node/lighthouse_network/Cargo.toml @@ -13,6 +13,7 @@ directory = { workspace = true } dirs = { workspace = true } discv5 = { workspace = true } either = { workspace = true } +eth2 = { workspace = true } ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } fnv = { workspace = true } @@ -23,6 +24,7 @@ itertools = { workspace = true } libp2p-mplex = "0.43" lighthouse_version = { workspace = true } local-ip-address = "0.6" +logging = { workspace = true } lru = { workspace = true } lru_cache = { workspace = true } metrics = { workspace = true } @@ -32,7 +34,6 @@ rand = { workspace = true } regex = { workspace = true } serde = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } smallvec = { workspace = true } snap = { workspace = true } ssz_types = { workspace = true } @@ -43,13 +44,12 @@ tiny-keccak = "2" tokio = { workspace = true } tokio-io-timeout = "1" tokio-util = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } unsigned-varint = { version = "0.8", features = ["codec"] } unused_port = { workspace = true } -# Local dependencies -void = "1.0.2" - [dependencies.libp2p] version = "0.55" default-features = false @@ -60,8 +60,6 @@ async-channel = { workspace = true } logging = { workspace = true } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } -slog-async = { workspace = true } -slog-term = { workspace = true } tempfile = { workspace = true } [features] diff --git a/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md b/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md deleted file mode 100644 index aba85f6184..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md +++ /dev/null @@ -1,386 +0,0 @@ -## 0.5 Sigma Prime fork -- Remove the beta tag from the v1.2 upgrade. - See [PR 6344](https://github.com/sigp/lighthouse/pull/6344) - -- Correct state inconsistencies with the mesh and connected peers due to the fanout mapping. - See [PR 6244](https://github.com/sigp/lighthouse/pull/6244) - -- Implement IDONTWANT messages as per [spec](https://github.com/libp2p/specs/pull/548). - See [PR 5422](https://github.com/sigp/lighthouse/pull/5422) - -- Attempt to publish to at least mesh_n peers when publishing a message when flood publish is disabled. - See [PR 5357](https://github.com/sigp/lighthouse/pull/5357). -- Drop `Publish` and `Forward` gossipsub stale messages when polling ConnectionHandler. - See [PR 5175](https://github.com/sigp/lighthouse/pull/5175). -- Apply back pressure by setting a limit in the ConnectionHandler message queue. - See [PR 5066](https://github.com/sigp/lighthouse/pull/5066). - -## 0.46.1 - -- Deprecate `Rpc` in preparation for removing it from the public API because it is an internal type. - See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). - -## 0.46.0 - -- Remove `fast_message_id_fn` mechanism from `Config`. - See [PR 4285](https://github.com/libp2p/rust-libp2p/pull/4285). -- Remove deprecated `gossipsub::Config::idle_timeout` in favor of `SwarmBuilder::idle_connection_timeout`. - See [PR 4642](https://github.com/libp2p/rust-libp2p/pull/4642). -- Return typed error from config builder. - See [PR 4445](https://github.com/libp2p/rust-libp2p/pull/4445). -- Process outbound stream before inbound stream in `EnabledHandler::poll(..)`. - See [PR 4778](https://github.com/libp2p/rust-libp2p/pull/4778). - -## 0.45.2 - -- Deprecate `gossipsub::Config::idle_timeout` in favor of `SwarmBuilder::idle_connection_timeout`. - See [PR 4648]. - - - -[PR 4648]: (https://github.com/libp2p/rust-libp2p/pull/4648) - - - -## 0.45.1 - -- Add getter function to o btain `TopicScoreParams`. - See [PR 4231]. - -[PR 4231]: https://github.com/libp2p/rust-libp2p/pull/4231 - -## 0.45.0 - -- Raise MSRV to 1.65. - See [PR 3715]. -- Remove deprecated items. See [PR 3862]. - -[PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 -[PR 3862]: https://github.com/libp2p/rust-libp2p/pull/3862 - -## 0.44.4 - -- Deprecate `metrics`, `protocol`, `subscription_filter`, `time_cache` modules to make them private. See [PR 3777]. -- Honor the `gossipsub::Config::support_floodsub` in all cases. - Previously, it was ignored when a custom protocol id was set via `gossipsub::Config::protocol_id`. - See [PR 3837]. - -[PR 3777]: https://github.com/libp2p/rust-libp2p/pull/3777 -[PR 3837]: https://github.com/libp2p/rust-libp2p/pull/3837 - -## 0.44.3 - -- Fix erroneously duplicate message IDs. See [PR 3716]. - -- Gracefully disable handler on stream errors. Deprecate a few variants of `HandlerError`. - See [PR 3625]. - -[PR 3716]: https://github.com/libp2p/rust-libp2p/pull/3716 -[PR 3625]: https://github.com/libp2p/rust-libp2p/pull/3325 - -## 0.44.2 - -- Signed messages now use sequential integers in the sequence number field. - See [PR 3551]. - -[PR 3551]: https://github.com/libp2p/rust-libp2p/pull/3551 - -## 0.44.1 - -- Migrate from `prost` to `quick-protobuf`. This removes `protoc` dependency. See [PR 3312]. - -[PR 3312]: https://github.com/libp2p/rust-libp2p/pull/3312 - -## 0.44.0 - -- Update to `prometheus-client` `v0.19.0`. See [PR 3207]. - -- Update to `libp2p-core` `v0.39.0`. - -- Update to `libp2p-swarm` `v0.42.0`. - -- Initialize `ProtocolConfig` via `GossipsubConfig`. See [PR 3381]. - -- Rename types as per [discussion 2174]. - `Gossipsub` has been renamed to `Behaviour`. - The `Gossipsub` prefix has been removed from various types like `GossipsubConfig` or `GossipsubMessage`. - It is preferred to import the gossipsub protocol as a module (`use libp2p::gossipsub;`), and refer to its types via `gossipsub::`. - For example: `gossipsub::Behaviour` or `gossipsub::RawMessage`. See [PR 3303]. - -[PR 3207]: https://github.com/libp2p/rust-libp2p/pull/3207/ -[PR 3303]: https://github.com/libp2p/rust-libp2p/pull/3303/ -[PR 3381]: https://github.com/libp2p/rust-libp2p/pull/3381/ -[discussion 2174]: https://github.com/libp2p/rust-libp2p/discussions/2174 - -## 0.43.0 - -- Update to `libp2p-core` `v0.38.0`. - -- Update to `libp2p-swarm` `v0.41.0`. - -- Update to `prost-codec` `v0.3.0`. - -- Refactoring GossipsubCodec to use common protobuf Codec. See [PR 3070]. - -- Replace `Gossipsub`'s `NetworkBehaviour` implementation `inject_*` methods with the new `on_*` methods. - See [PR 3011]. - -- Replace `GossipsubHandler`'s `ConnectionHandler` implementation `inject_*` methods with the new `on_*` methods. - See [PR 3085]. - -- Update `rust-version` to reflect the actual MSRV: 1.62.0. See [PR 3090]. - -[PR 3085]: https://github.com/libp2p/rust-libp2p/pull/3085 -[PR 3070]: https://github.com/libp2p/rust-libp2p/pull/3070 -[PR 3011]: https://github.com/libp2p/rust-libp2p/pull/3011 -[PR 3090]: https://github.com/libp2p/rust-libp2p/pull/3090 - -## 0.42.0 - -- Bump rand to 0.8 and quickcheck to 1. See [PR 2857]. - -- Update to `libp2p-core` `v0.37.0`. - -- Update to `libp2p-swarm` `v0.40.0`. - -[PR 2857]: https://github.com/libp2p/rust-libp2p/pull/2857 - -## 0.41.0 - -- Update to `libp2p-swarm` `v0.39.0`. - -- Update to `libp2p-core` `v0.36.0`. - -- Allow publishing with any `impl Into` as a topic. See [PR 2862]. - -[PR 2862]: https://github.com/libp2p/rust-libp2p/pull/2862 - -## 0.40.0 - -- Update prost requirement from 0.10 to 0.11 which no longer installs the protoc Protobuf compiler. - Thus you will need protoc installed locally. See [PR 2788]. - -- Update to `libp2p-swarm` `v0.38.0`. - -- Update to `libp2p-core` `v0.35.0`. - -- Update to `prometheus-client` `v0.18.0`. See [PR 2822]. - -[PR 2822]: https://github.com/libp2p/rust-libp2p/pull/2761/ -[PR 2788]: https://github.com/libp2p/rust-libp2p/pull/2788 - -## 0.39.0 - -- Update to `libp2p-core` `v0.34.0`. - -- Update to `libp2p-swarm` `v0.37.0`. - -- Allow for custom protocol ID via `GossipsubConfigBuilder::protocol_id()`. See [PR 2718]. - -[PR 2718]: https://github.com/libp2p/rust-libp2p/pull/2718/ - -## 0.38.1 - -- Fix duplicate connection id. See [PR 2702]. - -[PR 2702]: https://github.com/libp2p/rust-libp2p/pull/2702 - -## 0.38.0 - -- Update to `libp2p-core` `v0.33.0`. - -- Update to `libp2p-swarm` `v0.36.0`. - -- changed `TimeCache::contains_key` and `DuplicateCache::contains` to immutable methods. See [PR 2620]. - -- Update to `prometheus-client` `v0.16.0`. See [PR 2631]. - -[PR 2620]: https://github.com/libp2p/rust-libp2p/pull/2620 -[PR 2631]: https://github.com/libp2p/rust-libp2p/pull/2631 - -## 0.37.0 - -- Update to `libp2p-swarm` `v0.35.0`. - -- Fix gossipsub metric (see [PR 2558]). - -- Allow the user to set the buckets for the score histogram, and to adjust them from the score thresholds. See [PR 2595]. - -[PR 2558]: https://github.com/libp2p/rust-libp2p/pull/2558 -[PR 2595]: https://github.com/libp2p/rust-libp2p/pull/2595 - -## 0.36.0 [2022-02-22] - -- Update to `libp2p-core` `v0.32.0`. - -- Update to `libp2p-swarm` `v0.34.0`. - -- Move from `open-metrics-client` to `prometheus-client` (see [PR 2442]). - -- Emit gossip of all non empty topics (see [PR 2481]). - -- Merge NetworkBehaviour's inject_\* paired methods (see [PR 2445]). - -- Revert to wasm-timer (see [PR 2506]). - -- Do not overwrite msg's peers if put again into mcache (see [PR 2493]). - -[PR 2442]: https://github.com/libp2p/rust-libp2p/pull/2442 -[PR 2481]: https://github.com/libp2p/rust-libp2p/pull/2481 -[PR 2445]: https://github.com/libp2p/rust-libp2p/pull/2445 -[PR 2506]: https://github.com/libp2p/rust-libp2p/pull/2506 -[PR 2493]: https://github.com/libp2p/rust-libp2p/pull/2493 - -## 0.35.0 [2022-01-27] - -- Update dependencies. - -- Migrate to Rust edition 2021 (see [PR 2339]). - -- Add metrics for network and configuration performance analysis (see [PR 2346]). - -- Improve bandwidth performance by tracking IWANTs and reducing duplicate sends - (see [PR 2327]). - -- Implement `Serialize` and `Deserialize` for `MessageId` and `FastMessageId` (see [PR 2408]) - -- Fix `GossipsubConfigBuilder::build()` requiring `&self` to live for `'static` (see [PR 2409]) - -- Implement Unsubscribe backoff as per [libp2p specs PR 383] (see [PR 2403]). - -[PR 2346]: https://github.com/libp2p/rust-libp2p/pull/2346 -[PR 2339]: https://github.com/libp2p/rust-libp2p/pull/2339 -[PR 2327]: https://github.com/libp2p/rust-libp2p/pull/2327 -[PR 2408]: https://github.com/libp2p/rust-libp2p/pull/2408 -[PR 2409]: https://github.com/libp2p/rust-libp2p/pull/2409 -[PR 2403]: https://github.com/libp2p/rust-libp2p/pull/2403 -[libp2p specs PR 383]: https://github.com/libp2p/specs/pull/383 - -## 0.34.0 [2021-11-16] - -- Add topic and mesh metrics (see [PR 2316]). - -- Fix bug in internal peer's topics tracking (see [PR 2325]). - -- Use `instant` and `futures-timer` instead of `wasm-timer` (see [PR 2245]). - -- Update dependencies. - -[PR 2245]: https://github.com/libp2p/rust-libp2p/pull/2245 -[PR 2325]: https://github.com/libp2p/rust-libp2p/pull/2325 -[PR 2316]: https://github.com/libp2p/rust-libp2p/pull/2316 - -## 0.33.0 [2021-11-01] - -- Add an event to register peers that do not support the gossipsub protocol - [PR 2241](https://github.com/libp2p/rust-libp2p/pull/2241) - -- Make default features of `libp2p-core` optional. - [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) - -- Improve internal peer tracking. - [PR 2175](https://github.com/libp2p/rust-libp2p/pull/2175) - -- Update dependencies. - -- Allow `message_id_fn`s to accept closures that capture variables. - [PR 2103](https://github.com/libp2p/rust-libp2p/pull/2103) - -- Implement std::error::Error for error types. - [PR 2254](https://github.com/libp2p/rust-libp2p/pull/2254) - -## 0.32.0 [2021-07-12] - -- Update dependencies. - -- Reduce log levels across the crate to lessen noisiness of libp2p-gossipsub (see [PR 2101]). - -[PR 2101]: https://github.com/libp2p/rust-libp2p/pull/2101 - -## 0.31.0 [2021-05-17] - -- Keep connections to peers in a mesh alive. Allow closing idle connections to peers not in a mesh - [PR-2043]. - -[PR-2043]: https://github.com/libp2p/rust-libp2p/pull/2043https://github.com/libp2p/rust-libp2p/pull/2043 - -## 0.30.1 [2021-04-27] - -- Remove `regex-filter` feature flag thus always enabling `regex::RegexSubscriptionFilter` [PR - 2056](https://github.com/libp2p/rust-libp2p/pull/2056). - -## 0.30.0 [2021-04-13] - -- Update `libp2p-swarm`. - -- Update dependencies. - -## 0.29.0 [2021-03-17] - -- Update `libp2p-swarm`. - -- Update dependencies. - -## 0.28.0 [2021-02-15] - -- Prevent non-published messages being added to caches. - [PR 1930](https://github.com/libp2p/rust-libp2p/pull/1930) - -- Update dependencies. - -## 0.27.0 [2021-01-12] - -- Update dependencies. - -- Implement Gossipsub v1.1 specification. - [PR 1720](https://github.com/libp2p/rust-libp2p/pull/1720) - -## 0.26.0 [2020-12-17] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.25.0 [2020-11-25] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.24.0 [2020-11-09] - -- Update dependencies. - -## 0.23.0 [2020-10-16] - -- Update dependencies. - -## 0.22.0 [2020-09-09] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.21.0 [2020-08-18] - -- Add public API to list topics and peers. [PR 1677](https://github.com/libp2p/rust-libp2p/pull/1677). - -- Add message signing and extended privacy/validation configurations. [PR 1583](https://github.com/libp2p/rust-libp2p/pull/1583). - -- `Debug` instance for `Gossipsub`. [PR 1673](https://github.com/libp2p/rust-libp2p/pull/1673). - -- Bump `libp2p-core` and `libp2p-swarm` dependency. - -## 0.20.0 [2020-07-01] - -- Updated dependencies. - -## 0.19.3 [2020-06-23] - -- Maintenance release fixing linter warnings. - -## 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/beacon_node/lighthouse_network/gossipsub/Cargo.toml b/beacon_node/lighthouse_network/gossipsub/Cargo.toml deleted file mode 100644 index 239caae47a..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "gossipsub" -edition = "2021" -description = "Sigma prime's version of Gossipsub protocol for libp2p" -version = "0.5.0" -authors = ["Age Manning "] -license = "MIT" -repository = "https://github.com/sigp/lighthouse/" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[features] -wasm-bindgen = ["getrandom/js", "futures-timer/wasm-bindgen"] -rsa = [] - -[dependencies] -async-channel = { workspace = true } -asynchronous-codec = "0.7.0" -base64 = "0.21.7" -byteorder = "1.5.0" -bytes = "1.5" -either = "1.9" -fnv = "1.0.7" -futures = "0.3.30" -futures-timer = "3.0.2" -getrandom = "0.2.12" -hashlink = { workspace = true } -hex_fmt = "0.3.0" -libp2p = { version = "0.55", default-features = false } -prometheus-client = "0.22.0" -quick-protobuf = "0.8" -quick-protobuf-codec = "0.3" -rand = "0.8" -regex = "1.10.3" -serde = { version = "1", optional = true, features = ["derive"] } -sha2 = "0.10.8" -tracing = "0.1.37" -void = "1.0.2" -web-time = "1.1.0" - -[dev-dependencies] -quickcheck = { workspace = true } - -# Passing arguments to the docsrs builder in order to properly document cfg's. -# More information: https://docs.rs/about/builds#cross-compiling -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/beacon_node/lighthouse_network/gossipsub/src/backoff.rs b/beacon_node/lighthouse_network/gossipsub/src/backoff.rs deleted file mode 100644 index 0d77e2cd0f..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/backoff.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Data structure for efficiently storing known back-off's when pruning peers. -use crate::topic::TopicHash; -use libp2p::identity::PeerId; -use std::collections::{ - hash_map::{Entry, HashMap}, - HashSet, -}; -use std::time::Duration; -use web_time::Instant; - -#[derive(Copy, Clone)] -struct HeartbeatIndex(usize); - -/// Stores backoffs in an efficient manner. -pub(crate) struct BackoffStorage { - /// Stores backoffs and the index in backoffs_by_heartbeat per peer per topic. - backoffs: HashMap>, - /// Stores peer topic pairs per heartbeat (this is cyclic the current index is - /// heartbeat_index). - backoffs_by_heartbeat: Vec>, - /// The index in the backoffs_by_heartbeat vector corresponding to the current heartbeat. - heartbeat_index: HeartbeatIndex, - /// The heartbeat interval duration from the config. - heartbeat_interval: Duration, - /// Backoff slack from the config. - backoff_slack: u32, -} - -impl BackoffStorage { - fn heartbeats(d: &Duration, heartbeat_interval: &Duration) -> usize { - d.as_nanos().div_ceil(heartbeat_interval.as_nanos()) as usize - } - - pub(crate) fn new( - prune_backoff: &Duration, - heartbeat_interval: Duration, - backoff_slack: u32, - ) -> BackoffStorage { - // We add one additional slot for partial heartbeat - let max_heartbeats = - Self::heartbeats(prune_backoff, &heartbeat_interval) + backoff_slack as usize + 1; - BackoffStorage { - backoffs: HashMap::new(), - backoffs_by_heartbeat: vec![HashSet::new(); max_heartbeats], - heartbeat_index: HeartbeatIndex(0), - heartbeat_interval, - backoff_slack, - } - } - - /// Updates the backoff for a peer (if there is already a more restrictive backoff then this call - /// doesn't change anything). - pub(crate) fn update_backoff(&mut self, topic: &TopicHash, peer: &PeerId, time: Duration) { - let instant = Instant::now() + time; - let insert_into_backoffs_by_heartbeat = - |heartbeat_index: HeartbeatIndex, - backoffs_by_heartbeat: &mut Vec>, - heartbeat_interval, - backoff_slack| { - let pair = (topic.clone(), *peer); - let index = (heartbeat_index.0 - + Self::heartbeats(&time, heartbeat_interval) - + backoff_slack as usize) - % backoffs_by_heartbeat.len(); - backoffs_by_heartbeat[index].insert(pair); - HeartbeatIndex(index) - }; - match self.backoffs.entry(topic.clone()).or_default().entry(*peer) { - Entry::Occupied(mut o) => { - let (backoff, index) = o.get(); - if backoff < &instant { - let pair = (topic.clone(), *peer); - if let Some(s) = self.backoffs_by_heartbeat.get_mut(index.0) { - s.remove(&pair); - } - let index = insert_into_backoffs_by_heartbeat( - self.heartbeat_index, - &mut self.backoffs_by_heartbeat, - &self.heartbeat_interval, - self.backoff_slack, - ); - o.insert((instant, index)); - } - } - Entry::Vacant(v) => { - let index = insert_into_backoffs_by_heartbeat( - self.heartbeat_index, - &mut self.backoffs_by_heartbeat, - &self.heartbeat_interval, - self.backoff_slack, - ); - v.insert((instant, index)); - } - }; - } - - /// Checks if a given peer is backoffed for the given topic. This method respects the - /// configured BACKOFF_SLACK and may return true even if the backup is already over. - /// It is guaranteed to return false if the backoff is not over and eventually if enough time - /// passed true if the backoff is over. - /// - /// This method should be used for deciding if we can already send a GRAFT to a previously - /// backoffed peer. - pub(crate) fn is_backoff_with_slack(&self, topic: &TopicHash, peer: &PeerId) -> bool { - self.backoffs - .get(topic) - .is_some_and(|m| m.contains_key(peer)) - } - - pub(crate) fn get_backoff_time(&self, topic: &TopicHash, peer: &PeerId) -> Option { - Self::get_backoff_time_from_backoffs(&self.backoffs, topic, peer) - } - - fn get_backoff_time_from_backoffs( - backoffs: &HashMap>, - topic: &TopicHash, - peer: &PeerId, - ) -> Option { - backoffs - .get(topic) - .and_then(|m| m.get(peer).map(|(i, _)| *i)) - } - - /// Applies a heartbeat. That should be called regularly in intervals of length - /// `heartbeat_interval`. - pub(crate) fn heartbeat(&mut self) { - // Clean up backoffs_by_heartbeat - if let Some(s) = self.backoffs_by_heartbeat.get_mut(self.heartbeat_index.0) { - let backoffs = &mut self.backoffs; - let slack = self.heartbeat_interval * self.backoff_slack; - let now = Instant::now(); - s.retain(|(topic, peer)| { - let keep = match Self::get_backoff_time_from_backoffs(backoffs, topic, peer) { - Some(backoff_time) => backoff_time + slack > now, - None => false, - }; - if !keep { - //remove from backoffs - if let Entry::Occupied(mut m) = backoffs.entry(topic.clone()) { - if m.get_mut().remove(peer).is_some() && m.get().is_empty() { - m.remove(); - } - } - } - - keep - }); - } - - // Increase heartbeat index - self.heartbeat_index = - HeartbeatIndex((self.heartbeat_index.0 + 1) % self.backoffs_by_heartbeat.len()); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs b/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs deleted file mode 100644 index 7eb35cc49b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs +++ /dev/null @@ -1,3672 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::{ - cmp::{max, Ordering}, - collections::HashSet, - collections::VecDeque, - collections::{BTreeSet, HashMap}, - fmt, - net::IpAddr, - task::{Context, Poll}, - time::Duration, -}; - -use futures::FutureExt; -use hashlink::LinkedHashMap; -use prometheus_client::registry::Registry; -use rand::{seq::SliceRandom, thread_rng}; - -use libp2p::core::{ - multiaddr::Protocol::{Ip4, Ip6}, - transport::PortUse, - Endpoint, Multiaddr, -}; -use libp2p::identity::Keypair; -use libp2p::identity::PeerId; -use libp2p::swarm::{ - behaviour::{AddressChange, ConnectionClosed, ConnectionEstablished, FromSwarm}, - dial_opts::DialOpts, - ConnectionDenied, ConnectionId, NetworkBehaviour, NotifyHandler, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, -}; -use web_time::{Instant, SystemTime}; - -use crate::types::IDontWant; - -use super::gossip_promises::GossipPromises; -use super::handler::{Handler, HandlerEvent, HandlerIn}; -use super::mcache::MessageCache; -use super::metrics::{Churn, Config as MetricsConfig, Inclusion, Metrics, Penalty}; -use super::peer_score::{PeerScore, PeerScoreParams, PeerScoreThresholds, RejectReason}; -use super::protocol::SIGNING_PREFIX; -use super::rpc_proto::proto; -use super::subscription_filter::{AllowAllSubscriptionFilter, TopicSubscriptionFilter}; -use super::time_cache::DuplicateCache; -use super::topic::{Hasher, Topic, TopicHash}; -use super::transform::{DataTransform, IdentityTransform}; -use super::types::{ - ControlAction, FailedMessages, Message, MessageAcceptance, MessageId, PeerInfo, RawMessage, - Subscription, SubscriptionAction, -}; -use super::types::{Graft, IHave, IWant, PeerConnections, PeerKind, Prune}; -use super::{backoff::BackoffStorage, types::RpcSender}; -use super::{ - config::{Config, ValidationMode}, - types::RpcOut, -}; -use super::{PublishError, SubscriptionError, TopicScoreParams, ValidationError}; -use futures_timer::Delay; -use quick_protobuf::{MessageWrite, Writer}; -use std::{cmp::Ordering::Equal, fmt::Debug}; - -#[cfg(test)] -mod tests; - -/// IDONTWANT cache capacity. -const IDONTWANT_CAP: usize = 10_000; - -/// IDONTWANT timeout before removal. -const IDONTWANT_TIMEOUT: Duration = Duration::new(3, 0); - -/// Determines if published messages should be signed or not. -/// -/// Without signing, a number of privacy preserving modes can be selected. -/// -/// NOTE: The default validation settings are to require signatures. The [`ValidationMode`] -/// should be updated in the [`Config`] to allow for unsigned messages. -#[derive(Clone)] -pub enum MessageAuthenticity { - /// Message signing is enabled. The author will be the owner of the key and the sequence number - /// will be linearly increasing. - Signed(Keypair), - /// Message signing is disabled. - /// - /// The specified [`PeerId`] will be used as the author of all published messages. The sequence - /// number will be randomized. - Author(PeerId), - /// Message signing is disabled. - /// - /// A random [`PeerId`] will be used when publishing each message. The sequence number will be - /// randomized. - RandomAuthor, - /// Message signing is disabled. - /// - /// The author of the message and the sequence numbers are excluded from the message. - /// - /// NOTE: Excluding these fields may make these messages invalid by other nodes who - /// enforce validation of these fields. See [`ValidationMode`] in the [`Config`] - /// for how to customise this for rust-libp2p gossipsub. A custom `message_id` - /// function will need to be set to prevent all messages from a peer being filtered - /// as duplicates. - Anonymous, -} - -impl MessageAuthenticity { - /// Returns true if signing is enabled. - pub fn is_signing(&self) -> bool { - matches!(self, MessageAuthenticity::Signed(_)) - } - - pub fn is_anonymous(&self) -> bool { - matches!(self, MessageAuthenticity::Anonymous) - } -} - -/// Event that can be emitted by the gossipsub behaviour. -#[derive(Debug)] -pub enum Event { - /// A message has been received. - Message { - /// The peer that forwarded us this message. - propagation_source: PeerId, - /// The [`MessageId`] of the message. This should be referenced by the application when - /// validating a message (if required). - message_id: MessageId, - /// The decompressed message itself. - message: Message, - }, - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, - /// A peer that does not support gossipsub has connected. - GossipsubNotSupported { peer_id: PeerId }, - /// A peer is not able to download messages in time. - SlowPeer { - /// The peer_id - peer_id: PeerId, - /// The types and amounts of failed messages that are occurring for this peer. - failed_messages: FailedMessages, - }, -} - -/// A data structure for storing configuration for publishing messages. See [`MessageAuthenticity`] -/// for further details. -#[allow(clippy::large_enum_variant)] -enum PublishConfig { - Signing { - keypair: Keypair, - author: PeerId, - inline_key: Option>, - last_seq_no: SequenceNumber, - }, - Author(PeerId), - RandomAuthor, - Anonymous, -} - -/// A strictly linearly increasing sequence number. -/// -/// We start from the current time as unix timestamp in milliseconds. -#[derive(Debug)] -struct SequenceNumber(u64); - -impl SequenceNumber { - fn new() -> Self { - let unix_timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("time to be linear") - .as_nanos(); - - Self(unix_timestamp as u64) - } - - fn next(&mut self) -> u64 { - self.0 = self - .0 - .checked_add(1) - .expect("to not exhaust u64 space for sequence numbers"); - - self.0 - } -} - -impl PublishConfig { - pub(crate) fn get_own_id(&self) -> Option<&PeerId> { - match self { - Self::Signing { author, .. } => Some(author), - Self::Author(author) => Some(author), - _ => None, - } - } -} - -impl From for PublishConfig { - fn from(authenticity: MessageAuthenticity) -> Self { - match authenticity { - MessageAuthenticity::Signed(keypair) => { - let public_key = keypair.public(); - let key_enc = public_key.encode_protobuf(); - let key = if key_enc.len() <= 42 { - // The public key can be inlined in [`rpc_proto::proto::::Message::from`], so we don't include it - // specifically in the [`rpc_proto::proto::Message::key`] field. - None - } else { - // Include the protobuf encoding of the public key in the message. - Some(key_enc) - }; - - PublishConfig::Signing { - keypair, - author: public_key.to_peer_id(), - inline_key: key, - last_seq_no: SequenceNumber::new(), - } - } - MessageAuthenticity::Author(peer_id) => PublishConfig::Author(peer_id), - MessageAuthenticity::RandomAuthor => PublishConfig::RandomAuthor, - MessageAuthenticity::Anonymous => PublishConfig::Anonymous, - } - } -} - -/// Network behaviour that handles the gossipsub protocol. -/// -/// NOTE: Initialisation requires a [`MessageAuthenticity`] and [`Config`] instance. If -/// message signing is disabled, the [`ValidationMode`] in the config should be adjusted to an -/// appropriate level to accept unsigned messages. -/// -/// The DataTransform trait allows applications to optionally add extra encoding/decoding -/// functionality to the underlying messages. This is intended for custom compression algorithms. -/// -/// The TopicSubscriptionFilter allows applications to implement specific filters on topics to -/// prevent unwanted messages being propagated and evaluated. -pub struct Behaviour { - /// Configuration providing gossipsub performance parameters. - config: Config, - - /// Events that need to be yielded to the outside when polling. - events: VecDeque>, - - /// Information used for publishing messages. - publish_config: PublishConfig, - - /// An LRU Time cache for storing seen messages (based on their ID). This cache prevents - /// duplicates from being propagated to the application and on the network. - duplicate_cache: DuplicateCache, - - /// A set of connected peers, indexed by their [`PeerId`] tracking both the [`PeerKind`] and - /// the set of [`ConnectionId`]s. - connected_peers: HashMap, - - /// A set of all explicit peers. These are peers that remain connected and we unconditionally - /// forward messages to, outside of the scoring system. - explicit_peers: HashSet, - - /// A list of peers that have been blacklisted by the user. - /// Messages are not sent to and are rejected from these peers. - blacklisted_peers: HashSet, - - /// Overlay network of connected peers - Maps topics to connected gossipsub peers. - mesh: HashMap>, - - /// Map of topics to list of peers that we publish to, but don't subscribe to. - fanout: HashMap>, - - /// The last publish time for fanout topics. - fanout_last_pub: HashMap, - - ///Storage for backoffs - backoffs: BackoffStorage, - - /// Message cache for the last few heartbeats. - mcache: MessageCache, - - /// Heartbeat interval stream. - heartbeat: Delay, - - /// Number of heartbeats since the beginning of time; this allows us to amortize some resource - /// clean up -- eg backoff clean up. - heartbeat_ticks: u64, - - /// We remember all peers we found through peer exchange, since those peers are not considered - /// as safe as randomly discovered outbound peers. This behaviour diverges from the go - /// implementation to avoid possible love bombing attacks in PX. When disconnecting peers will - /// be removed from this list which may result in a true outbound rediscovery. - px_peers: HashSet, - - /// Set of connected outbound peers (we only consider true outbound peers found through - /// discovery and not by PX). - outbound_peers: HashSet, - - /// Stores optional peer score data together with thresholds and decay interval. - peer_score: Option<(PeerScore, PeerScoreThresholds, Delay)>, - - /// Counts the number of `IHAVE` received from each peer since the last heartbeat. - count_received_ihave: HashMap, - - /// Counts the number of `IWANT` that we sent the each peer since the last heartbeat. - count_sent_iwant: HashMap, - - /// Short term cache for published message ids. This is used for penalizing peers sending - /// our own messages back if the messages are anonymous or use a random author. - published_message_ids: DuplicateCache, - - /// The filter used to handle message subscriptions. - subscription_filter: F, - - /// A general transformation function that can be applied to data received from the wire before - /// calculating the message-id and sending to the application. This is designed to allow the - /// user to implement arbitrary topic-based compression algorithms. - data_transform: D, - - /// Keep track of a set of internal metrics relating to gossipsub. - metrics: Option, - - /// Tracks the numbers of failed messages per peer-id. - failed_messages: HashMap, - - /// Tracks recently sent `IWANT` messages and checks if peers respond to them. - gossip_promises: GossipPromises, -} - -impl Behaviour -where - D: DataTransform + Default, - F: TopicSubscriptionFilter + Default, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`]. This has no subscription filter and uses no compression. - pub fn new(privacy: MessageAuthenticity, config: Config) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - None, - F::default(), - D::default(), - ) - } - - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`]. This has no subscription filter and uses no compression. - /// Metrics can be evaluated by passing a reference to a [`Registry`]. - pub fn new_with_metrics( - privacy: MessageAuthenticity, - config: Config, - metrics_registry: &mut Registry, - metrics_config: MetricsConfig, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - Some((metrics_registry, metrics_config)), - F::default(), - D::default(), - ) - } -} - -impl Behaviour -where - D: DataTransform + Default, - F: TopicSubscriptionFilter, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom subscription filter. - pub fn new_with_subscription_filter( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - subscription_filter: F, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - metrics, - subscription_filter, - D::default(), - ) - } -} - -impl Behaviour -where - D: DataTransform, - F: TopicSubscriptionFilter + Default, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom data transform. - pub fn new_with_transform( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - data_transform: D, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - metrics, - F::default(), - data_transform, - ) - } -} - -impl Behaviour -where - D: DataTransform, - F: TopicSubscriptionFilter, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom subscription filter and data transform. - pub fn new_with_subscription_filter_and_transform( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - subscription_filter: F, - data_transform: D, - ) -> Result { - // Set up the router given the configuration settings. - - // We do not allow configurations where a published message would also be rejected if it - // were received locally. - validate_config(&privacy, config.validation_mode())?; - - Ok(Behaviour { - metrics: metrics.map(|(registry, cfg)| Metrics::new(registry, cfg)), - events: VecDeque::new(), - publish_config: privacy.into(), - duplicate_cache: DuplicateCache::new(config.duplicate_cache_time()), - explicit_peers: HashSet::new(), - blacklisted_peers: HashSet::new(), - mesh: HashMap::new(), - fanout: HashMap::new(), - fanout_last_pub: HashMap::new(), - backoffs: BackoffStorage::new( - &config.prune_backoff(), - config.heartbeat_interval(), - config.backoff_slack(), - ), - mcache: MessageCache::new(config.history_gossip(), config.history_length()), - heartbeat: Delay::new(config.heartbeat_interval() + config.heartbeat_initial_delay()), - heartbeat_ticks: 0, - px_peers: HashSet::new(), - outbound_peers: HashSet::new(), - peer_score: None, - count_received_ihave: HashMap::new(), - count_sent_iwant: HashMap::new(), - connected_peers: HashMap::new(), - published_message_ids: DuplicateCache::new(config.published_message_ids_cache_time()), - config, - subscription_filter, - data_transform, - failed_messages: Default::default(), - gossip_promises: Default::default(), - }) - } -} - -impl Behaviour -where - D: DataTransform + Send + 'static, - F: TopicSubscriptionFilter + Send + 'static, -{ - /// Lists the hashes of the topics we are currently subscribed to. - pub fn topics(&self) -> impl Iterator { - self.mesh.keys() - } - - /// Lists all mesh peers for a certain topic hash. - pub fn mesh_peers(&self, topic_hash: &TopicHash) -> impl Iterator { - self.mesh.get(topic_hash).into_iter().flat_map(|x| x.iter()) - } - - pub fn all_mesh_peers(&self) -> impl Iterator { - let mut res = BTreeSet::new(); - for peers in self.mesh.values() { - res.extend(peers); - } - res.into_iter() - } - - /// Lists all known peers and their associated subscribed topics. - pub fn all_peers(&self) -> impl Iterator)> { - self.connected_peers - .iter() - .map(|(peer_id, peer)| (peer_id, peer.topics.iter().collect())) - } - - /// Lists all known peers and their associated protocol. - pub fn peer_protocol(&self) -> impl Iterator { - self.connected_peers.iter().map(|(k, v)| (k, &v.kind)) - } - - /// Returns the gossipsub score for a given peer, if one exists. - pub fn peer_score(&self, peer_id: &PeerId) -> Option { - self.peer_score - .as_ref() - .map(|(score, ..)| score.score(peer_id)) - } - - /// Subscribe to a topic. - /// - /// Returns [`Ok(true)`] if the subscription worked. Returns [`Ok(false)`] if we were already - /// subscribed. - pub fn subscribe(&mut self, topic: &Topic) -> Result { - tracing::debug!(%topic, "Subscribing to topic"); - let topic_hash = topic.hash(); - if !self.subscription_filter.can_subscribe(&topic_hash) { - return Err(SubscriptionError::NotAllowed); - } - - if self.mesh.contains_key(&topic_hash) { - tracing::debug!(%topic, "Topic is already in the mesh"); - return Ok(false); - } - - // send subscription request to all peers - for (peer_id, peer) in self.connected_peers.iter_mut() { - tracing::debug!(%peer_id, "Sending SUBSCRIBE to peer"); - - peer.sender.subscribe(topic_hash.clone()); - } - - // call JOIN(topic) - // this will add new peers to the mesh for the topic - self.join(&topic_hash); - tracing::debug!(%topic, "Subscribed to topic"); - Ok(true) - } - - /// Unsubscribes from a topic. - /// - /// Returns [`Ok(true)`] if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: &Topic) -> Result { - tracing::debug!(%topic, "Unsubscribing from topic"); - let topic_hash = topic.hash(); - - if !self.mesh.contains_key(&topic_hash) { - tracing::debug!(topic=%topic_hash, "Already unsubscribed from topic"); - // we are not subscribed - return Ok(false); - } - - // announce to all peers - for (peer_id, peer) in self.connected_peers.iter_mut() { - tracing::debug!(%peer_id, "Sending UNSUBSCRIBE to peer"); - peer.sender.unsubscribe(topic_hash.clone()); - } - - // call LEAVE(topic) - // this will remove the topic from the mesh - self.leave(&topic_hash); - - tracing::debug!(topic=%topic_hash, "Unsubscribed from topic"); - Ok(true) - } - - /// Publishes a message with multiple topics to the network. - pub fn publish( - &mut self, - topic: impl Into, - data: impl Into>, - ) -> Result { - let data = data.into(); - let topic = topic.into(); - - // Transform the data before building a raw_message. - let transformed_data = self - .data_transform - .outbound_transform(&topic, data.clone())?; - - let raw_message = self.build_raw_message(topic, transformed_data)?; - - // calculate the message id from the un-transformed data - let msg_id = self.config.message_id(&Message { - source: raw_message.source, - data, // the uncompressed form - sequence_number: raw_message.sequence_number, - topic: raw_message.topic.clone(), - }); - - // check that the size doesn't exceed the max transmission size - if raw_message.raw_protobuf_len() > self.config.max_transmit_size() { - return Err(PublishError::MessageTooLarge); - } - - // Check the if the message has been published before - if self.duplicate_cache.contains(&msg_id) { - // This message has already been seen. We don't re-publish messages that have already - // been published on the network. - tracing::warn!( - message=%msg_id, - "Not publishing a message that has already been published" - ); - return Err(PublishError::Duplicate); - } - - tracing::trace!(message=%msg_id, "Publishing message"); - - let topic_hash = raw_message.topic.clone(); - - let mut peers_on_topic = self - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(&topic_hash)) - .map(|(peer_id, _)| peer_id) - .peekable(); - - if peers_on_topic.peek().is_none() { - return Err(PublishError::InsufficientPeers); - } - - let mut recipient_peers = HashSet::new(); - - if self.config.flood_publish() { - // Forward to all peers above score and all explicit peers - recipient_peers.extend(peers_on_topic.filter(|p| { - self.explicit_peers.contains(*p) - || !self.score_below_threshold(p, |ts| ts.publish_threshold).0 - })); - } else { - match self.mesh.get(&topic_hash) { - // Mesh peers - Some(mesh_peers) => { - // We have a mesh set. We want to make sure to publish to at least `mesh_n` - // peers (if possible). - let needed_extra_peers = self.config.mesh_n().saturating_sub(mesh_peers.len()); - - if needed_extra_peers > 0 { - // We don't have `mesh_n` peers in our mesh, we will randomly select extras - // and publish to them. - - // Get a random set of peers that are appropriate to send messages too. - let peer_list = get_random_peers( - &self.connected_peers, - &topic_hash, - needed_extra_peers, - |peer| { - !mesh_peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self - .score_below_threshold(peer, |pst| pst.publish_threshold) - .0 - }, - ); - recipient_peers.extend(peer_list); - } - - recipient_peers.extend(mesh_peers); - } - // Gossipsub peers - None => { - tracing::debug!(topic=%topic_hash, "Topic not in the mesh"); - // `fanout_peers` is always non-empty if it's `Some`. - let fanout_peers = self - .fanout - .get(&topic_hash) - .map(|peers| if peers.is_empty() { None } else { Some(peers) }) - .unwrap_or(None); - // If we have fanout peers add them to the map. - if let Some(peers) = fanout_peers { - for peer in peers { - recipient_peers.insert(*peer); - } - } else { - // We have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n(); - let new_peers = - get_random_peers(&self.connected_peers, &topic_hash, mesh_n, { - |p| { - !self.explicit_peers.contains(p) - && !self - .score_below_threshold(p, |pst| pst.publish_threshold) - .0 - } - }); - // Add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - tracing::debug!(%peer, "Peer added to fanout"); - recipient_peers.insert(peer); - } - } - // We are publishing to fanout peers - update the time we published - self.fanout_last_pub - .insert(topic_hash.clone(), Instant::now()); - } - } - - // Explicit peers that are part of the topic - recipient_peers - .extend(peers_on_topic.filter(|peer_id| self.explicit_peers.contains(peer_id))); - - // Floodsub peers - for (peer, connections) in &self.connected_peers { - if connections.kind == PeerKind::Floodsub - && !self - .score_below_threshold(peer, |ts| ts.publish_threshold) - .0 - { - recipient_peers.insert(*peer); - } - } - } - - // If the message isn't a duplicate and we have sent it to some peers add it to the - // duplicate cache and memcache. - self.duplicate_cache.insert(msg_id.clone()); - self.mcache.put(&msg_id, raw_message.clone()); - - // If the message is anonymous or has a random author add it to the published message ids - // cache. - if let PublishConfig::RandomAuthor | PublishConfig::Anonymous = self.publish_config { - if !self.config.allow_self_origin() { - self.published_message_ids.insert(msg_id.clone()); - } - } - - // Send to peers we know are subscribed to the topic. - let mut publish_failed = true; - for peer_id in recipient_peers.iter() { - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - tracing::trace!(peer=%peer_id, "Sending message to peer"); - match peer.sender.publish( - raw_message.clone(), - self.config.publish_queue_duration(), - self.metrics.as_mut(), - ) { - Ok(_) => publish_failed = false, - Err(_) => { - self.failed_messages.entry(*peer_id).or_default().priority += 1; - - tracing::warn!(peer_id=%peer_id, "Publish queue full. Could not publish to peer"); - // Downscore the peer due to failed message. - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - } - } - } else { - tracing::error!(peer_id = %peer_id, - "Could not send PUBLISH, peer doesn't exist in connected peer list"); - } - } - - if recipient_peers.is_empty() { - return Err(PublishError::InsufficientPeers); - } - - if publish_failed { - return Err(PublishError::AllQueuesFull(recipient_peers.len())); - } - - // Broadcast IDONTWANT messages - if raw_message.raw_protobuf_len() > self.config.idontwant_message_size_threshold() { - self.send_idontwant(&raw_message, &msg_id, raw_message.source.as_ref()); - } - - tracing::debug!(message=%msg_id, "Published message"); - - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_published_message(&topic_hash); - } - - Ok(msg_id) - } - - /// This function should be called when [`Config::validate_messages()`] is `true` after - /// the message got validated by the caller. Messages are stored in the ['Memcache'] and - /// validation is expected to be fast enough that the messages should still exist in the cache. - /// There are three possible validation outcomes and the outcome is given in acceptance. - /// - /// If acceptance = [`MessageAcceptance::Accept`] the message will get propagated to the - /// network. The `propagation_source` parameter indicates who the message was received by and - /// will not be forwarded back to that peer. - /// - /// If acceptance = [`MessageAcceptance::Reject`] the message will be deleted from the memcache - /// and the Pâ‚„ penalty will be applied to the `propagation_source`. - // - /// If acceptance = [`MessageAcceptance::Ignore`] the message will be deleted from the memcache - /// but no Pâ‚„ penalty will be applied. - /// - /// This function will return true if the message was found in the cache and false if was not - /// in the cache anymore. - /// - /// This should only be called once per message. - pub fn report_message_validation_result( - &mut self, - msg_id: &MessageId, - propagation_source: &PeerId, - acceptance: MessageAcceptance, - ) -> Result { - let reject_reason = match acceptance { - MessageAcceptance::Accept => { - let (raw_message, originating_peers) = match self.mcache.validate(msg_id) { - Some((raw_message, originating_peers)) => { - (raw_message.clone(), originating_peers) - } - None => { - tracing::warn!( - message=%msg_id, - "Message not in cache. Ignoring forwarding" - ); - if let Some(metrics) = self.metrics.as_mut() { - metrics.memcache_miss(); - } - return Ok(false); - } - }; - - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_msg_validation(&raw_message.topic, &acceptance); - } - - self.forward_msg( - msg_id, - raw_message, - Some(propagation_source), - originating_peers, - )?; - return Ok(true); - } - MessageAcceptance::Reject => RejectReason::ValidationFailed, - MessageAcceptance::Ignore => RejectReason::ValidationIgnored, - }; - - if let Some((raw_message, originating_peers)) = self.mcache.remove(msg_id) { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_msg_validation(&raw_message.topic, &acceptance); - } - - // Tell peer_score about reject - // Reject the original source, and any duplicates we've seen from other peers. - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.reject_message( - propagation_source, - msg_id, - &raw_message.topic, - reject_reason, - ); - for peer in originating_peers.iter() { - peer_score.reject_message(peer, msg_id, &raw_message.topic, reject_reason); - } - } - Ok(true) - } else { - tracing::warn!(message=%msg_id, "Rejected message not in cache"); - Ok(false) - } - } - - /// Register topics to ensure metrics are recorded correctly for these topics. - pub fn register_topics_for_metrics(&mut self, topics: Vec) { - if let Some(metrics) = &mut self.metrics { - metrics.register_allowed_topics(topics); - } - } - - /// Adds a new peer to the list of explicitly connected peers. - pub fn add_explicit_peer(&mut self, peer_id: &PeerId) { - tracing::debug!(peer=%peer_id, "Adding explicit peer"); - - self.explicit_peers.insert(*peer_id); - - self.check_explicit_peer_connection(peer_id); - } - - /// This removes the peer from explicitly connected peers, note that this does not disconnect - /// the peer. - pub fn remove_explicit_peer(&mut self, peer_id: &PeerId) { - tracing::debug!(peer=%peer_id, "Removing explicit peer"); - self.explicit_peers.remove(peer_id); - } - - /// Blacklists a peer. All messages from this peer will be rejected and any message that was - /// created by this peer will be rejected. - pub fn blacklist_peer(&mut self, peer_id: &PeerId) { - if self.blacklisted_peers.insert(*peer_id) { - tracing::debug!(peer=%peer_id, "Peer has been blacklisted"); - } - } - - /// Removes a peer from the blacklist if it has previously been blacklisted. - pub fn remove_blacklisted_peer(&mut self, peer_id: &PeerId) { - if self.blacklisted_peers.remove(peer_id) { - tracing::debug!(peer=%peer_id, "Peer has been removed from the blacklist"); - } - } - - /// Activates the peer scoring system with the given parameters. This will reset all scores - /// if there was already another peer scoring system activated. Returns an error if the - /// params are not valid or if they got already set. - pub fn with_peer_score( - &mut self, - params: PeerScoreParams, - threshold: PeerScoreThresholds, - ) -> Result<(), String> { - self.with_peer_score_and_message_delivery_time_callback(params, threshold, None) - } - - /// Activates the peer scoring system with the given parameters and a message delivery time - /// callback. Returns an error if the parameters got already set. - pub fn with_peer_score_and_message_delivery_time_callback( - &mut self, - params: PeerScoreParams, - threshold: PeerScoreThresholds, - callback: Option, - ) -> Result<(), String> { - params.validate()?; - threshold.validate()?; - - if self.peer_score.is_some() { - return Err("Peer score set twice".into()); - } - - let interval = Delay::new(params.decay_interval); - let peer_score = PeerScore::new_with_message_delivery_time_callback(params, callback); - self.peer_score = Some((peer_score, threshold, interval)); - Ok(()) - } - - /// Sets scoring parameters for a topic. - /// - /// The [`Self::with_peer_score()`] must first be called to initialise peer scoring. - pub fn set_topic_params( - &mut self, - topic: Topic, - params: TopicScoreParams, - ) -> Result<(), &'static str> { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.set_topic_params(topic.hash(), params); - Ok(()) - } else { - Err("Peer score must be initialised with `with_peer_score()`") - } - } - - /// Returns a scoring parameters for a topic if existent. - pub fn get_topic_params(&self, topic: &Topic) -> Option<&TopicScoreParams> { - self.peer_score.as_ref()?.0.get_topic_params(&topic.hash()) - } - - /// Sets the application specific score for a peer. Returns true if scoring is active and - /// the peer is connected or if the score of the peer is not yet expired, false otherwise. - pub fn set_application_score(&mut self, peer_id: &PeerId, new_score: f64) -> bool { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.set_application_score(peer_id, new_score) - } else { - false - } - } - - /// Gossipsub JOIN(topic) - adds topic peers to mesh and sends them GRAFT messages. - fn join(&mut self, topic_hash: &TopicHash) { - tracing::debug!(topic=%topic_hash, "Running JOIN for topic"); - - // if we are already in the mesh, return - if self.mesh.contains_key(topic_hash) { - tracing::debug!(topic=%topic_hash, "JOIN: The topic is already in the mesh, ignoring JOIN"); - return; - } - - let mut added_peers = HashSet::new(); - - if let Some(m) = self.metrics.as_mut() { - m.joined(topic_hash) - } - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - if let Some((_, mut peers)) = self.fanout.remove_entry(topic_hash) { - tracing::debug!( - topic=%topic_hash, - "JOIN: Removing peers from the fanout for topic" - ); - - // remove explicit peers, peers with negative scores, and backoffed peers - peers.retain(|p| { - !self.explicit_peers.contains(p) - && !self.score_below_threshold(p, |_| 0.0).0 - && !self.backoffs.is_backoff_with_slack(topic_hash, p) - }); - - // Add up to mesh_n of them them to the mesh - // NOTE: These aren't randomly added, currently FIFO - let add_peers = std::cmp::min(peers.len(), self.config.mesh_n()); - tracing::debug!( - topic=%topic_hash, - "JOIN: Adding {:?} peers from the fanout for topic", - add_peers - ); - added_peers.extend(peers.iter().take(add_peers)); - - self.mesh.insert( - topic_hash.clone(), - peers.into_iter().take(add_peers).collect(), - ); - - // remove the last published time - self.fanout_last_pub.remove(topic_hash); - } - - let fanaout_added = added_peers.len(); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Fanout, fanaout_added) - } - - // check if we need to get more peers, which we randomly select - if added_peers.len() < self.config.mesh_n() { - // get the peers - let new_peers = get_random_peers( - &self.connected_peers, - topic_hash, - self.config.mesh_n() - added_peers.len(), - |peer| { - !added_peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self.score_below_threshold(peer, |_| 0.0).0 - && !self.backoffs.is_backoff_with_slack(topic_hash, peer) - }, - ); - added_peers.extend(new_peers.clone()); - // add them to the mesh - tracing::debug!( - "JOIN: Inserting {:?} random peers into the mesh", - new_peers.len() - ); - let mesh_peers = self.mesh.entry(topic_hash.clone()).or_default(); - mesh_peers.extend(new_peers); - } - - let random_added = added_peers.len() - fanaout_added; - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, random_added) - } - - for peer_id in added_peers { - // Send a GRAFT control message - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(&peer_id, topic_hash.clone()); - } - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - tracing::debug!(peer=%peer_id, "JOIN: Sending Graft message to peer"); - peer.sender.graft(Graft { - topic_hash: topic_hash.clone(), - }); - } else { - tracing::error!(peer = %peer_id, - "Could not send GRAFT, peer doesn't exist in connected peer list"); - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - peer_id, - vec![topic_hash], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - let mesh_peers = self.mesh_peers(topic_hash).count(); - if let Some(m) = self.metrics.as_mut() { - m.set_mesh_peers(topic_hash, mesh_peers) - } - - tracing::debug!(topic=%topic_hash, "Completed JOIN for topic"); - } - - /// Creates a PRUNE gossipsub action. - fn make_prune( - &mut self, - topic_hash: &TopicHash, - peer: &PeerId, - do_px: bool, - on_unsubscribe: bool, - ) -> Prune { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.prune(peer, topic_hash.clone()); - } - - match self.connected_peers.get(peer).map(|v| &v.kind) { - Some(PeerKind::Floodsub) => { - tracing::error!("Attempted to prune a Floodsub peer"); - } - Some(PeerKind::Gossipsub) => { - // GossipSub v1.0 -- no peer exchange, the peer won't be able to parse it anyway - return Prune { - topic_hash: topic_hash.clone(), - peers: Vec::new(), - backoff: None, - }; - } - None => { - tracing::error!("Attempted to Prune an unknown peer"); - } - _ => {} // Gossipsub 1.1 peer perform the `Prune` - } - - // Select peers for peer exchange - let peers = if do_px { - get_random_peers( - &self.connected_peers, - topic_hash, - self.config.prune_peers(), - |p| p != peer && !self.score_below_threshold(p, |_| 0.0).0, - ) - .into_iter() - .map(|p| PeerInfo { peer_id: Some(p) }) - .collect() - } else { - Vec::new() - }; - - let backoff = if on_unsubscribe { - self.config.unsubscribe_backoff() - } else { - self.config.prune_backoff() - }; - - // update backoff - self.backoffs.update_backoff(topic_hash, peer, backoff); - - Prune { - topic_hash: topic_hash.clone(), - peers, - backoff: Some(backoff.as_secs()), - } - } - - /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages. - fn leave(&mut self, topic_hash: &TopicHash) { - tracing::debug!(topic=%topic_hash, "Running LEAVE for topic"); - - // If our mesh contains the topic, send prune to peers and delete it from the mesh - if let Some((_, peers)) = self.mesh.remove_entry(topic_hash) { - if let Some(m) = self.metrics.as_mut() { - m.left(topic_hash) - } - for peer_id in peers { - // Send a PRUNE control message - let prune = self.make_prune(topic_hash, &peer_id, self.config.do_px(), true); - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - tracing::debug!(%peer_id, "LEAVE: Sending PRUNE to peer"); - peer.sender.prune(prune); - } else { - tracing::error!(peer = %peer_id, - "Could not send PRUNE, peer doesn't exist in connected peer list"); - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_removed_from_mesh( - peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - tracing::debug!(topic=%topic_hash, "Completed LEAVE for topic"); - } - - /// Checks if the given peer is still connected and if not dials the peer again. - fn check_explicit_peer_connection(&mut self, peer_id: &PeerId) { - if !self.connected_peers.contains_key(peer_id) { - // Connect to peer - tracing::debug!(peer=%peer_id, "Connecting to explicit peer"); - self.events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(*peer_id).build(), - }); - } - } - - /// Determines if a peer's score is below a given `PeerScoreThreshold` chosen via the - /// `threshold` parameter. - fn score_below_threshold( - &self, - peer_id: &PeerId, - threshold: impl Fn(&PeerScoreThresholds) -> f64, - ) -> (bool, f64) { - Self::score_below_threshold_from_scores(&self.peer_score, peer_id, threshold) - } - - fn score_below_threshold_from_scores( - peer_score: &Option<(PeerScore, PeerScoreThresholds, Delay)>, - peer_id: &PeerId, - threshold: impl Fn(&PeerScoreThresholds) -> f64, - ) -> (bool, f64) { - if let Some((peer_score, thresholds, ..)) = peer_score { - let score = peer_score.score(peer_id); - if score < threshold(thresholds) { - return (true, score); - } - (false, score) - } else { - (false, 0.0) - } - } - - /// Handles an IHAVE control message. Checks our cache of messages. If the message is unknown, - /// requests it with an IWANT control message. - fn handle_ihave(&mut self, peer_id: &PeerId, ihave_msgs: Vec<(TopicHash, Vec)>) { - // We ignore IHAVE gossip from any peer whose score is below the gossip threshold - if let (true, score) = self.score_below_threshold(peer_id, |pst| pst.gossip_threshold) { - tracing::debug!( - peer=%peer_id, - %score, - "IHAVE: ignoring peer with score below threshold" - ); - return; - } - - // IHAVE flood protection - let peer_have = self.count_received_ihave.entry(*peer_id).or_insert(0); - *peer_have += 1; - if *peer_have > self.config.max_ihave_messages() { - tracing::debug!( - peer=%peer_id, - "IHAVE: peer has advertised too many times ({}) within this heartbeat \ - interval; ignoring", - *peer_have - ); - return; - } - - if let Some(iasked) = self.count_sent_iwant.get(peer_id) { - if *iasked >= self.config.max_ihave_length() { - tracing::debug!( - peer=%peer_id, - "IHAVE: peer has already advertised too many messages ({}); ignoring", - *iasked - ); - return; - } - } - - tracing::trace!(peer=%peer_id, "Handling IHAVE for peer"); - - let mut iwant_ids = HashSet::new(); - - let want_message = |id: &MessageId| { - if self.duplicate_cache.contains(id) { - return false; - } - - !self.gossip_promises.contains(id) - }; - - for (topic, ids) in ihave_msgs { - // only process the message if we are subscribed - if !self.mesh.contains_key(&topic) { - tracing::debug!( - %topic, - "IHAVE: Ignoring IHAVE - Not subscribed to topic" - ); - continue; - } - - for id in ids.into_iter().filter(want_message) { - // have not seen this message and are not currently requesting it - if iwant_ids.insert(id) { - // Register the IWANT metric - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_iwant(&topic); - } - } - } - } - - if !iwant_ids.is_empty() { - let iasked = self.count_sent_iwant.entry(*peer_id).or_insert(0); - let mut iask = iwant_ids.len(); - if *iasked + iask > self.config.max_ihave_length() { - iask = self.config.max_ihave_length().saturating_sub(*iasked); - } - - // Send the list of IWANT control messages - tracing::debug!( - peer=%peer_id, - "IHAVE: Asking for {} out of {} messages from peer", - iask, - iwant_ids.len() - ); - - // Ask in random order - let mut iwant_ids_vec: Vec<_> = iwant_ids.into_iter().collect(); - let mut rng = thread_rng(); - iwant_ids_vec.partial_shuffle(&mut rng, iask); - - iwant_ids_vec.truncate(iask); - *iasked += iask; - - self.gossip_promises.add_promise( - *peer_id, - &iwant_ids_vec, - Instant::now() + self.config.iwant_followup_time(), - ); - - if let Some(peer) = &mut self.connected_peers.get_mut(peer_id) { - tracing::trace!( - peer=%peer_id, - "IHAVE: Asking for the following messages from peer: {:?}", - iwant_ids_vec - ); - - if peer - .sender - .iwant(IWant { - message_ids: iwant_ids_vec, - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IWANT"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IWANT, peer doesn't exist in connected peer list"); - } - } - tracing::trace!(peer=%peer_id, "Completed IHAVE handling for peer"); - } - - /// Handles an IWANT control message. Checks our cache of messages. If the message exists it is - /// forwarded to the requesting peer. - fn handle_iwant(&mut self, peer_id: &PeerId, iwant_msgs: Vec) { - // We ignore IWANT gossip from any peer whose score is below the gossip threshold - if let (true, score) = self.score_below_threshold(peer_id, |pst| pst.gossip_threshold) { - tracing::debug!( - peer=%peer_id, - "IWANT: ignoring peer with score below threshold [score = {}]", - score - ); - return; - } - - tracing::debug!(peer=%peer_id, "Handling IWANT for peer"); - - for id in iwant_msgs { - // If we have it and the IHAVE count is not above the threshold, - // forward the message. - if let Some((msg, count)) = self - .mcache - .get_with_iwant_counts(&id, peer_id) - .map(|(msg, count)| (msg.clone(), count)) - { - if count > self.config.gossip_retransimission() { - tracing::debug!( - peer=%peer_id, - message=%id, - "IWANT: Peer has asked for message too many times; ignoring request" - ); - } else if let Some(peer) = &mut self.connected_peers.get_mut(peer_id) { - if peer.dont_send_received.get(&id).is_some() { - tracing::debug!(%peer_id, message=%id, "Peer already sent IDONTWANT for this message"); - continue; - } - - tracing::debug!(peer=%peer_id, "IWANT: Sending cached messages to peer"); - if peer - .sender - .forward( - msg, - self.config.forward_queue_duration(), - self.metrics.as_mut(), - ) - .is_err() - { - // Downscore the peer - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment the failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IWANT, peer doesn't exist in connected peer list"); - } - } - } - tracing::debug!(peer=%peer_id, "Completed IWANT handling for peer"); - } - - /// Handles GRAFT control messages. If subscribed to the topic, adds the peer to mesh, if not, - /// responds with PRUNE messages. - fn handle_graft(&mut self, peer_id: &PeerId, topics: Vec) { - tracing::debug!(peer=%peer_id, "Handling GRAFT message for peer"); - - let mut to_prune_topics = HashSet::new(); - - let mut do_px = self.config.do_px(); - - // For each topic, if a peer has grafted us, then we necessarily must be in their mesh - // and they must be subscribed to the topic. Ensure we have recorded the mapping. - for topic in &topics { - let Some(connected_peer) = self.connected_peers.get_mut(peer_id) else { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling graft"); - return; - }; - if connected_peer.topics.insert(topic.clone()) { - if let Some(m) = self.metrics.as_mut() { - m.inc_topic_peers(topic); - } - } - } - - // we don't GRAFT to/from explicit peers; complain loudly if this happens - if self.explicit_peers.contains(peer_id) { - tracing::warn!(peer=%peer_id, "GRAFT: ignoring request from direct peer"); - // this is possibly a bug from non-reciprocal configuration; send a PRUNE for all topics - to_prune_topics = topics.into_iter().collect(); - // but don't PX - do_px = false - } else { - let (below_zero, score) = self.score_below_threshold(peer_id, |_| 0.0); - let now = Instant::now(); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // if the peer is already in the mesh ignore the graft - if peers.contains(peer_id) { - tracing::debug!( - peer=%peer_id, - topic=%&topic_hash, - "GRAFT: Received graft for peer that is already in topic" - ); - continue; - } - - // make sure we are not backing off that peer - if let Some(backoff_time) = self.backoffs.get_backoff_time(&topic_hash, peer_id) - { - if backoff_time > now { - tracing::warn!( - peer=%peer_id, - "[Penalty] Peer attempted graft within backoff time, penalizing" - ); - // add behavioural penalty - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_score_penalty(Penalty::GraftBackoff); - } - peer_score.add_penalty(peer_id, 1); - - // check the flood cutoff - // See: https://github.com/rust-lang/rust-clippy/issues/10061 - #[allow(unknown_lints, clippy::unchecked_duration_subtraction)] - let flood_cutoff = (backoff_time - + self.config.graft_flood_threshold()) - - self.config.prune_backoff(); - if flood_cutoff > now { - //extra penalty - peer_score.add_penalty(peer_id, 1); - } - } - // no PX - do_px = false; - - to_prune_topics.insert(topic_hash.clone()); - continue; - } - } - - // check the score - if below_zero { - // we don't GRAFT peers with negative score - tracing::debug!( - peer=%peer_id, - %score, - topic=%topic_hash, - "GRAFT: ignoring peer with negative score" - ); - // we do send them PRUNE however, because it's a matter of protocol correctness - to_prune_topics.insert(topic_hash.clone()); - // but we won't PX to them - do_px = false; - continue; - } - - // check mesh upper bound and only allow graft if the upper bound is not reached or - // if it is an outbound peer - if peers.len() >= self.config.mesh_n_high() - && !self.outbound_peers.contains(peer_id) - { - to_prune_topics.insert(topic_hash.clone()); - continue; - } - - // add peer to the mesh - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "GRAFT: Mesh link added for peer in topic" - ); - - if peers.insert(*peer_id) { - if let Some(m) = self.metrics.as_mut() { - m.peers_included(&topic_hash, Inclusion::Subscribed, 1) - } - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - *peer_id, - vec![&topic_hash], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(peer_id, topic_hash); - } - } else { - // don't do PX when there is an unknown topic to avoid leaking our peers - do_px = false; - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "GRAFT: Received graft for unknown topic from peer" - ); - // spam hardening: ignore GRAFTs for unknown topics - continue; - } - } - } - - if !to_prune_topics.is_empty() { - // build the prune messages to send - let on_unsubscribe = false; - - let mut sender = match self.connected_peers.get_mut(peer_id) { - Some(connected_peer) => connected_peer.sender.clone(), - None => { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling graft and obtaining a sender"); - return; - } - }; - - for prune in to_prune_topics - .iter() - .map(|t| self.make_prune(t, peer_id, do_px, on_unsubscribe)) - { - sender.prune(prune); - } - // Send the prune messages to the peer - tracing::debug!( - peer=%peer_id, - "GRAFT: Not subscribed to topics - Sending PRUNE to peer" - ); - } - tracing::debug!(peer=%peer_id, "Completed GRAFT handling for peer"); - } - - fn remove_peer_from_mesh( - &mut self, - peer_id: &PeerId, - topic_hash: &TopicHash, - backoff: Option, - always_update_backoff: bool, - reason: Churn, - ) { - let mut update_backoff = always_update_backoff; - if let Some(peers) = self.mesh.get_mut(topic_hash) { - // remove the peer if it exists in the mesh - if peers.remove(peer_id) { - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "PRUNE: Removing peer from the mesh for topic" - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, reason, 1) - } - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.prune(peer_id, topic_hash.clone()); - } - - update_backoff = true; - - // inform the handler - peer_removed_from_mesh( - *peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - if update_backoff { - let time = if let Some(backoff) = backoff { - Duration::from_secs(backoff) - } else { - self.config.prune_backoff() - }; - // is there a backoff specified by the peer? if so obey it. - self.backoffs.update_backoff(topic_hash, peer_id, time); - } - } - - /// Handles PRUNE control messages. Removes peer from the mesh. - fn handle_prune( - &mut self, - peer_id: &PeerId, - prune_data: Vec<(TopicHash, Vec, Option)>, - ) { - tracing::debug!(peer=%peer_id, "Handling PRUNE message for peer"); - let (below_threshold, score) = - self.score_below_threshold(peer_id, |pst| pst.accept_px_threshold); - for (topic_hash, px, backoff) in prune_data { - self.remove_peer_from_mesh(peer_id, &topic_hash, backoff, true, Churn::Prune); - - if self.mesh.contains_key(&topic_hash) { - //connect to px peers - if !px.is_empty() { - // we ignore PX from peers with insufficient score - if below_threshold { - tracing::debug!( - peer=%peer_id, - %score, - topic=%topic_hash, - "PRUNE: ignoring PX from peer with insufficient score" - ); - continue; - } - - // NOTE: We cannot dial any peers from PX currently as we typically will not - // know their multiaddr. Until SignedRecords are spec'd this - // remains a stub. By default `config.prune_peers()` is set to zero and - // this is skipped. If the user modifies this, this will only be able to - // dial already known peers (from an external discovery mechanism for - // example). - if self.config.prune_peers() > 0 { - self.px_connect(px); - } - } - } - } - tracing::debug!(peer=%peer_id, "Completed PRUNE handling for peer"); - } - - fn px_connect(&mut self, mut px: Vec) { - let n = self.config.prune_peers(); - // Ignore peerInfo with no ID - // - //TODO: Once signed records are spec'd: Can we use peerInfo without any IDs if they have a - // signed peer record? - px.retain(|p| p.peer_id.is_some()); - if px.len() > n { - // only use at most prune_peers many random peers - let mut rng = thread_rng(); - px.partial_shuffle(&mut rng, n); - px = px.into_iter().take(n).collect(); - } - - for p in px { - // TODO: Once signed records are spec'd: extract signed peer record if given and handle - // it, see https://github.com/libp2p/specs/pull/217 - if let Some(peer_id) = p.peer_id { - // mark as px peer - self.px_peers.insert(peer_id); - - // dial peer - self.events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(peer_id).build(), - }); - } - } - } - - /// Applies some basic checks to whether this message is valid. Does not apply user validation - /// checks. - fn message_is_valid( - &mut self, - msg_id: &MessageId, - raw_message: &mut RawMessage, - propagation_source: &PeerId, - ) -> bool { - tracing::debug!( - peer=%propagation_source, - message=%msg_id, - "Handling message from peer" - ); - - // Reject any message from a blacklisted peer - if self.blacklisted_peers.contains(propagation_source) { - tracing::debug!( - peer=%propagation_source, - "Rejecting message from blacklisted peer" - ); - self.gossip_promises - .reject_message(msg_id, &RejectReason::BlackListedPeer); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.reject_message( - propagation_source, - msg_id, - &raw_message.topic, - RejectReason::BlackListedPeer, - ); - } - return false; - } - - // Also reject any message that originated from a blacklisted peer - if let Some(source) = raw_message.source.as_ref() { - if self.blacklisted_peers.contains(source) { - tracing::debug!( - peer=%propagation_source, - %source, - "Rejecting message from peer because of blacklisted source" - ); - self.handle_invalid_message( - propagation_source, - raw_message, - RejectReason::BlackListedSource, - ); - return false; - } - } - - // If we are not validating messages, assume this message is validated - // This will allow the message to be gossiped without explicitly calling - // `validate_message`. - if !self.config.validate_messages() { - raw_message.validated = true; - } - - // reject messages claiming to be from ourselves but not locally published - let self_published = !self.config.allow_self_origin() - && if let Some(own_id) = self.publish_config.get_own_id() { - own_id != propagation_source && raw_message.source.as_ref() == Some(own_id) - } else { - self.published_message_ids.contains(msg_id) - }; - - if self_published { - tracing::debug!( - message=%msg_id, - source=%propagation_source, - "Dropping message claiming to be from self but forwarded from source" - ); - self.handle_invalid_message(propagation_source, raw_message, RejectReason::SelfOrigin); - return false; - } - - true - } - - /// Handles a newly received [`RawMessage`]. - /// - /// Forwards the message to all peers in the mesh. - fn handle_received_message( - &mut self, - mut raw_message: RawMessage, - propagation_source: &PeerId, - ) { - // Record the received metric - if let Some(metrics) = self.metrics.as_mut() { - metrics.msg_recvd_unfiltered(&raw_message.topic, raw_message.raw_protobuf_len()); - } - - // Try and perform the data transform to the message. If it fails, consider it invalid. - let message = match self.data_transform.inbound_transform(raw_message.clone()) { - Ok(message) => message, - Err(e) => { - tracing::debug!("Invalid message. Transform error: {:?}", e); - // Reject the message and return - self.handle_invalid_message( - propagation_source, - &raw_message, - RejectReason::ValidationError(ValidationError::TransformFailed), - ); - return; - } - }; - - // Calculate the message id on the transformed data. - let msg_id = self.config.message_id(&message); - - if let Some(metrics) = self.metrics.as_mut() { - if let Some(peer) = self.connected_peers.get_mut(propagation_source) { - // Record if we received a message that we already sent a IDONTWANT for to the peer - if peer.dont_send_sent.contains_key(&msg_id) { - metrics.register_idontwant_messages_ignored_per_topic(&raw_message.topic); - } - } - } - - // Check the validity of the message - // Peers get penalized if this message is invalid. We don't add it to the duplicate cache - // and instead continually penalize peers that repeatedly send this message. - if !self.message_is_valid(&msg_id, &mut raw_message, propagation_source) { - return; - } - - if !self.duplicate_cache.insert(msg_id.clone()) { - tracing::debug!(message=%msg_id, "Message already received, ignoring"); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.duplicated_message(propagation_source, &msg_id, &message.topic); - } - self.mcache.observe_duplicate(&msg_id, propagation_source); - // track metrics for the source of the duplicates - if let Some(metrics) = self.metrics.as_mut() { - if self - .mesh - .get(&message.topic) - .is_some_and(|peers| peers.contains(propagation_source)) - { - // duplicate was received from a mesh peer - metrics.mesh_duplicates(&message.topic); - } else if self - .gossip_promises - .contains_peer(&msg_id, propagation_source) - { - // duplicate was received from an iwant request - metrics.iwant_duplicates(&message.topic); - } else { - tracing::warn!( - messsage=%msg_id, - peer=%propagation_source, - topic=%message.topic, - "Peer should not have sent message" - ); - } - } - return; - } - - // Broadcast IDONTWANT messages - if raw_message.raw_protobuf_len() > self.config.idontwant_message_size_threshold() { - self.send_idontwant(&raw_message, &msg_id, Some(propagation_source)); - } - - tracing::debug!( - message=%msg_id, - "Put message in duplicate_cache and resolve promises" - ); - - // Record the received message with the metrics - if let Some(metrics) = self.metrics.as_mut() { - metrics.msg_recvd(&message.topic); - } - - // Consider the message as delivered for gossip promises. - self.gossip_promises.message_delivered(&msg_id); - - // Tells score that message arrived (but is maybe not fully validated yet). - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.validate_message(propagation_source, &msg_id, &message.topic); - } - - // Add the message to our memcache - self.mcache.put(&msg_id, raw_message.clone()); - - // Dispatch the message to the user if we are subscribed to any of the topics - if self.mesh.contains_key(&message.topic) { - tracing::debug!("Sending received message to user"); - self.events - .push_back(ToSwarm::GenerateEvent(Event::Message { - propagation_source: *propagation_source, - message_id: msg_id.clone(), - message, - })); - } else { - tracing::debug!( - topic=%message.topic, - "Received message on a topic we are not subscribed to" - ); - return; - } - - // forward the message to mesh peers, if no validation is required - if !self.config.validate_messages() { - if self - .forward_msg( - &msg_id, - raw_message, - Some(propagation_source), - HashSet::new(), - ) - .is_err() - { - tracing::error!("Failed to forward message. Too large"); - } - tracing::debug!(message=%msg_id, "Completed message handling for message"); - } - } - - // Handles invalid messages received. - fn handle_invalid_message( - &mut self, - propagation_source: &PeerId, - raw_message: &RawMessage, - reject_reason: RejectReason, - ) { - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_invalid_message(&raw_message.topic); - } - - if let Ok(message) = self.data_transform.inbound_transform(raw_message.clone()) { - let message_id = self.config.message_id(&message); - - peer_score.reject_message( - propagation_source, - &message_id, - &message.topic, - reject_reason, - ); - - self.gossip_promises - .reject_message(&message_id, &reject_reason); - } else { - // The message is invalid, we reject it ignoring any gossip promises. If a peer is - // advertising this message via an IHAVE and it's invalid it will be double - // penalized, one for sending us an invalid and again for breaking a promise. - peer_score.reject_invalid_message(propagation_source, &raw_message.topic); - } - } - } - - /// Handles received subscriptions. - fn handle_received_subscriptions( - &mut self, - subscriptions: &[Subscription], - propagation_source: &PeerId, - ) { - tracing::debug!( - source=%propagation_source, - "Handling subscriptions: {:?}", - subscriptions, - ); - - let mut unsubscribed_peers = Vec::new(); - - let Some(peer) = self.connected_peers.get_mut(propagation_source) else { - tracing::error!( - peer=%propagation_source, - "Subscription by unknown peer" - ); - return; - }; - - // Collect potential graft topics for the peer. - let mut topics_to_graft = Vec::new(); - - // Notify the application about the subscription, after the grafts are sent. - let mut application_event = Vec::new(); - - let filtered_topics = match self - .subscription_filter - .filter_incoming_subscriptions(subscriptions, &peer.topics) - { - Ok(topics) => topics, - Err(s) => { - tracing::error!( - peer=%propagation_source, - "Subscription filter error: {}; ignoring RPC from peer", - s - ); - return; - } - }; - - for subscription in filtered_topics { - // get the peers from the mapping, or insert empty lists if the topic doesn't exist - let topic_hash = &subscription.topic_hash; - - match subscription.action { - SubscriptionAction::Subscribe => { - // add to the peer_topics mapping - if peer.topics.insert(topic_hash.clone()) { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Adding gossip peer to topic" - ); - - if let Some(m) = self.metrics.as_mut() { - m.inc_topic_peers(topic_hash); - } - } - // if the mesh needs peers add the peer to the mesh - if !self.explicit_peers.contains(propagation_source) - && peer.kind.is_gossipsub() - && !Self::score_below_threshold_from_scores( - &self.peer_score, - propagation_source, - |_| 0.0, - ) - .0 - && !self - .backoffs - .is_backoff_with_slack(topic_hash, propagation_source) - { - if let Some(peers) = self.mesh.get_mut(topic_hash) { - if peers.len() < self.config.mesh_n_low() - && peers.insert(*propagation_source) - { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Adding peer to the mesh for topic" - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Subscribed, 1) - } - // send graft to the peer - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "Sending GRAFT to peer for topic" - ); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(propagation_source, topic_hash.clone()); - } - topics_to_graft.push(topic_hash.clone()); - } - } - } - // generates a subscription event to be polled - application_event.push(ToSwarm::GenerateEvent(Event::Subscribed { - peer_id: *propagation_source, - topic: topic_hash.clone(), - })); - } - SubscriptionAction::Unsubscribe => { - // remove topic from the peer_topics mapping - if peer.topics.remove(topic_hash) { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Removing gossip peer from topic" - ); - - if let Some(m) = self.metrics.as_mut() { - m.dec_topic_peers(topic_hash); - } - } - - unsubscribed_peers.push((*propagation_source, topic_hash.clone())); - // generate an unsubscribe event to be polled - application_event.push(ToSwarm::GenerateEvent(Event::Unsubscribed { - peer_id: *propagation_source, - topic: topic_hash.clone(), - })); - } - } - } - - // remove unsubscribed peers from the mesh and fanout if they exist there. - for (peer_id, topic_hash) in unsubscribed_peers { - self.fanout - .get_mut(&topic_hash) - .map(|peers| peers.remove(&peer_id)); - self.remove_peer_from_mesh(&peer_id, &topic_hash, None, false, Churn::Unsub); - } - - // Potentially inform the handler if we have added this peer to a mesh for the first time. - let topics_joined = topics_to_graft.iter().collect::>(); - if !topics_joined.is_empty() { - peer_added_to_mesh( - *propagation_source, - topics_joined, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - // If we need to send grafts to peer, do so immediately, rather than waiting for the - // heartbeat. - if let Some(peer) = &mut self.connected_peers.get_mut(propagation_source) { - for topic_hash in topics_to_graft.into_iter() { - peer.sender.graft(Graft { topic_hash }); - } - } else { - tracing::error!(peer = %propagation_source, - "Could not send GRAFT, peer doesn't exist in connected peer list"); - } - - // Notify the application of the subscriptions - for event in application_event { - self.events.push_back(event); - } - - tracing::trace!( - source=%propagation_source, - "Completed handling subscriptions from source" - ); - } - - /// Applies penalties to peers that did not respond to our IWANT requests. - fn apply_iwant_penalties(&mut self) { - if let Some((peer_score, ..)) = &mut self.peer_score { - for (peer, count) in self.gossip_promises.get_broken_promises() { - // We do not apply penalties to nodes that have disconnected. - if self.connected_peers.contains_key(&peer) { - peer_score.add_penalty(&peer, count); - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_score_penalty(Penalty::BrokenPromise); - } - } - } - } - } - - /// Heartbeat function which shifts the memcache and updates the mesh. - fn heartbeat(&mut self) { - tracing::debug!("Starting heartbeat"); - let start = Instant::now(); - - // Every heartbeat we sample the send queues to add to our metrics. We do this intentionally - // before we add all the gossip from this heartbeat in order to gain a true measure of - // steady-state size of the queues. - if let Some(m) = &mut self.metrics { - for sender_queue in self.connected_peers.values_mut().map(|v| &v.sender) { - m.observe_priority_queue_size(sender_queue.priority_len()); - m.observe_non_priority_queue_size(sender_queue.non_priority_len()); - } - } - - self.heartbeat_ticks += 1; - - let mut to_graft = HashMap::new(); - let mut to_prune = HashMap::new(); - let mut no_px = HashSet::new(); - - // clean up expired backoffs - self.backoffs.heartbeat(); - - // clean up ihave counters - self.count_sent_iwant.clear(); - self.count_received_ihave.clear(); - - // apply iwant penalties - self.apply_iwant_penalties(); - - // check connections to explicit peers - if self.heartbeat_ticks % self.config.check_explicit_peers_ticks() == 0 { - for p in self.explicit_peers.clone() { - self.check_explicit_peer_connection(&p); - } - } - - // Cache the scores of all connected peers, and record metrics for current penalties. - let mut scores = HashMap::with_capacity(self.connected_peers.len()); - if let Some((peer_score, ..)) = &self.peer_score { - for peer_id in self.connected_peers.keys() { - scores - .entry(peer_id) - .or_insert_with(|| peer_score.metric_score(peer_id, self.metrics.as_mut())); - } - } - - // maintain the mesh for each topic - for (topic_hash, peers) in self.mesh.iter_mut() { - let explicit_peers = &self.explicit_peers; - let backoffs = &self.backoffs; - let outbound_peers = &self.outbound_peers; - - // drop all peers with negative score, without PX - // if there is at some point a stable retain method for BTreeSet the following can be - // written more efficiently with retain. - let mut to_remove_peers = Vec::new(); - for peer_id in peers.iter() { - let peer_score = *scores.get(peer_id).unwrap_or(&0.0); - - // Record the score per mesh - if let Some(metrics) = self.metrics.as_mut() { - metrics.observe_mesh_peers_score(topic_hash, peer_score); - } - - if peer_score < 0.0 { - tracing::debug!( - peer=%peer_id, - score=%peer_score, - topic=%topic_hash, - "HEARTBEAT: Prune peer with negative score" - ); - - let current_topic = to_prune.entry(*peer_id).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - no_px.insert(*peer_id); - to_remove_peers.push(*peer_id); - } - } - - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, Churn::BadScore, to_remove_peers.len()) - } - - for peer_id in to_remove_peers { - peers.remove(&peer_id); - } - - // too little peers - add some - if peers.len() < self.config.mesh_n_low() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Mesh low. Topic contains: {} needs: {}", - peers.len(), - self.config.mesh_n_low() - ); - // not enough peers - get mesh_n - current_length more - let desired_peers = self.config.mesh_n() - peers.len(); - let peer_list = - get_random_peers(&self.connected_peers, topic_hash, desired_peers, |peer| { - !peers.contains(peer) - && !explicit_peers.contains(peer) - && !backoffs.is_backoff_with_slack(topic_hash, peer) - && *scores.get(peer).unwrap_or(&0.0) >= 0.0 - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!("Updating mesh, new mesh: {:?}", peer_list); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, peer_list.len()) - } - peers.extend(peer_list); - } - - // too many peers - remove some - if peers.len() > self.config.mesh_n_high() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Mesh high. Topic contains: {} needs: {}", - peers.len(), - self.config.mesh_n_high() - ); - let excess_peer_no = peers.len() - self.config.mesh_n(); - - // shuffle the peers and then sort by score ascending beginning with the worst - let mut rng = thread_rng(); - let mut shuffled = peers.iter().copied().collect::>(); - shuffled.shuffle(&mut rng); - shuffled.sort_by(|p1, p2| { - let score_p1 = *scores.get(p1).unwrap_or(&0.0); - let score_p2 = *scores.get(p2).unwrap_or(&0.0); - - score_p1.partial_cmp(&score_p2).unwrap_or(Ordering::Equal) - }); - // shuffle everything except the last retain_scores many peers (the best ones) - shuffled[..peers.len() - self.config.retain_scores()].shuffle(&mut rng); - - // count total number of outbound peers - let mut outbound = { - let outbound_peers = &self.outbound_peers; - shuffled - .iter() - .filter(|p| outbound_peers.contains(*p)) - .count() - }; - - // remove the first excess_peer_no allowed (by outbound restrictions) peers adding - // them to to_prune - let mut removed = 0; - for peer in shuffled { - if removed == excess_peer_no { - break; - } - if self.outbound_peers.contains(&peer) { - if outbound <= self.config.mesh_outbound_min() { - // do not remove anymore outbound peers - continue; - } - // an outbound peer gets removed - outbound -= 1; - } - - // remove the peer - peers.remove(&peer); - let current_topic = to_prune.entry(peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - removed += 1; - } - - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, Churn::Excess, removed) - } - } - - // do we have enough outbound peers? - if peers.len() >= self.config.mesh_n_low() { - // count number of outbound peers we have - let outbound = { peers.iter().filter(|p| outbound_peers.contains(*p)).count() }; - - // if we have not enough outbound peers, graft to some new outbound peers - if outbound < self.config.mesh_outbound_min() { - let needed = self.config.mesh_outbound_min() - outbound; - let peer_list = - get_random_peers(&self.connected_peers, topic_hash, needed, |peer| { - !peers.contains(peer) - && !explicit_peers.contains(peer) - && !backoffs.is_backoff_with_slack(topic_hash, peer) - && *scores.get(peer).unwrap_or(&0.0) >= 0.0 - && outbound_peers.contains(peer) - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!("Updating mesh, new mesh: {:?}", peer_list); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Outbound, peer_list.len()) - } - peers.extend(peer_list); - } - } - - // should we try to improve the mesh with opportunistic grafting? - if self.heartbeat_ticks % self.config.opportunistic_graft_ticks() == 0 - && peers.len() > 1 - && self.peer_score.is_some() - { - if let Some((_, thresholds, _)) = &self.peer_score { - // Opportunistic grafting works as follows: we check the median score of peers - // in the mesh; if this score is below the opportunisticGraftThreshold, we - // select a few peers at random with score over the median. - // The intention is to (slowly) improve an underperforming mesh by introducing - // good scoring peers that may have been gossiping at us. This allows us to - // get out of sticky situations where we are stuck with poor peers and also - // recover from churn of good peers. - - // now compute the median peer score in the mesh - let mut peers_by_score: Vec<_> = peers.iter().collect(); - peers_by_score.sort_by(|p1, p2| { - let p1_score = *scores.get(p1).unwrap_or(&0.0); - let p2_score = *scores.get(p2).unwrap_or(&0.0); - p1_score.partial_cmp(&p2_score).unwrap_or(Equal) - }); - - let middle = peers_by_score.len() / 2; - let median = if peers_by_score.len() % 2 == 0 { - let sub_middle_peer = *peers_by_score - .get(middle - 1) - .expect("middle < vector length and middle > 0 since peers.len() > 0"); - let sub_middle_score = *scores.get(sub_middle_peer).unwrap_or(&0.0); - let middle_peer = - *peers_by_score.get(middle).expect("middle < vector length"); - let middle_score = *scores.get(middle_peer).unwrap_or(&0.0); - - (sub_middle_score + middle_score) * 0.5 - } else { - *scores - .get(*peers_by_score.get(middle).expect("middle < vector length")) - .unwrap_or(&0.0) - }; - - // if the median score is below the threshold, select a better peer (if any) and - // GRAFT - if median < thresholds.opportunistic_graft_threshold { - let peer_list = get_random_peers( - &self.connected_peers, - topic_hash, - self.config.opportunistic_graft_peers(), - |peer_id| { - !peers.contains(peer_id) - && !explicit_peers.contains(peer_id) - && !backoffs.is_backoff_with_slack(topic_hash, peer_id) - && *scores.get(peer_id).unwrap_or(&0.0) > median - }, - ); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!( - topic=%topic_hash, - "Opportunistically graft in topic with peers {:?}", - peer_list - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, peer_list.len()) - } - peers.extend(peer_list); - } - } - } - // Register the final count of peers in the mesh - if let Some(m) = self.metrics.as_mut() { - m.set_mesh_peers(topic_hash, peers.len()) - } - } - - // remove expired fanout topics - { - let fanout = &mut self.fanout; // help the borrow checker - let fanout_ttl = self.config.fanout_ttl(); - self.fanout_last_pub.retain(|topic_hash, last_pub_time| { - if *last_pub_time + fanout_ttl < Instant::now() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Fanout topic removed due to timeout" - ); - fanout.remove(topic_hash); - return false; - } - true - }); - } - - // maintain fanout - // check if our peers are still a part of the topic - for (topic_hash, peers) in self.fanout.iter_mut() { - let mut to_remove_peers = Vec::new(); - let publish_threshold = match &self.peer_score { - Some((_, thresholds, _)) => thresholds.publish_threshold, - _ => 0.0, - }; - for peer_id in peers.iter() { - // is the peer still subscribed to the topic? - let peer_score = *scores.get(peer_id).unwrap_or(&0.0); - match self.connected_peers.get(peer_id) { - Some(peer) => { - if !peer.topics.contains(topic_hash) || peer_score < publish_threshold { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Peer removed from fanout for topic" - ); - to_remove_peers.push(*peer_id); - } - } - None => { - // remove if the peer has disconnected - to_remove_peers.push(*peer_id); - } - } - } - for to_remove in to_remove_peers { - peers.remove(&to_remove); - } - - // not enough peers - if peers.len() < self.config.mesh_n() { - tracing::debug!( - "HEARTBEAT: Fanout low. Contains: {:?} needs: {:?}", - peers.len(), - self.config.mesh_n() - ); - let needed_peers = self.config.mesh_n() - peers.len(); - let explicit_peers = &self.explicit_peers; - let new_peers = - get_random_peers(&self.connected_peers, topic_hash, needed_peers, |peer_id| { - !peers.contains(peer_id) - && !explicit_peers.contains(peer_id) - && *scores.get(peer_id).unwrap_or(&0.0) < publish_threshold - }); - peers.extend(new_peers); - } - } - - if self.peer_score.is_some() { - tracing::trace!("Mesh message deliveries: {:?}", { - self.mesh - .iter() - .map(|(t, peers)| { - ( - t.clone(), - peers - .iter() - .map(|p| { - ( - *p, - self.peer_score - .as_ref() - .expect("peer_score.is_some()") - .0 - .mesh_message_deliveries(p, t) - .unwrap_or(0.0), - ) - }) - .collect::>(), - ) - }) - .collect::>>() - }) - } - - self.emit_gossip(); - - // send graft/prunes - if !to_graft.is_empty() | !to_prune.is_empty() { - self.send_graft_prune(to_graft, to_prune, no_px); - } - - // shift the memcache - self.mcache.shift(); - - // Report expired messages - for (peer_id, failed_messages) in self.failed_messages.drain() { - tracing::debug!("Peer couldn't consume messages: {:?}", failed_messages); - self.events - .push_back(ToSwarm::GenerateEvent(Event::SlowPeer { - peer_id, - failed_messages, - })); - } - self.failed_messages.shrink_to_fit(); - - // Flush stale IDONTWANTs. - for peer in self.connected_peers.values_mut() { - while let Some((_front, instant)) = peer.dont_send_received.front() { - if (*instant + IDONTWANT_TIMEOUT) >= Instant::now() { - break; - } else { - peer.dont_send_received.pop_front(); - } - } - // If metrics are not enabled, this queue would be empty. - while let Some((_front, instant)) = peer.dont_send_sent.front() { - if (*instant + IDONTWANT_TIMEOUT) >= Instant::now() { - break; - } else { - peer.dont_send_sent.pop_front(); - } - } - } - - tracing::debug!("Completed Heartbeat"); - if let Some(metrics) = self.metrics.as_mut() { - let duration = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); - metrics.observe_heartbeat_duration(duration); - } - } - - /// Emits gossip - Send IHAVE messages to a random set of gossip peers. This is applied to mesh - /// and fanout peers - fn emit_gossip(&mut self) { - let mut rng = thread_rng(); - for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { - let mut message_ids = self.mcache.get_gossip_message_ids(topic_hash); - if message_ids.is_empty() { - continue; - } - - // if we are emitting more than GossipSubMaxIHaveLength message_ids, truncate the list - if message_ids.len() > self.config.max_ihave_length() { - // we do the truncation (with shuffling) per peer below - tracing::debug!( - "too many messages for gossip; will truncate IHAVE list ({} messages)", - message_ids.len() - ); - } else { - // shuffle to emit in random order - message_ids.shuffle(&mut rng); - } - - // dynamic number of peers to gossip based on `gossip_factor` with minimum `gossip_lazy` - let n_map = |m| { - max( - self.config.gossip_lazy(), - (self.config.gossip_factor() * m as f64) as usize, - ) - }; - // get gossip_lazy random peers - let to_msg_peers = - get_random_peers_dynamic(&self.connected_peers, topic_hash, n_map, |peer| { - !peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self.score_below_threshold(peer, |ts| ts.gossip_threshold).0 - }); - - tracing::debug!("Gossiping IHAVE to {} peers", to_msg_peers.len()); - - for peer_id in to_msg_peers { - let mut peer_message_ids = message_ids.clone(); - - if peer_message_ids.len() > self.config.max_ihave_length() { - // We do this per peer so that we emit a different set for each peer. - // we have enough redundancy in the system that this will significantly increase - // the message coverage when we do truncate. - peer_message_ids.partial_shuffle(&mut rng, self.config.max_ihave_length()); - peer_message_ids.truncate(self.config.max_ihave_length()); - } - - // send an IHAVE message - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - if peer - .sender - .ihave(IHave { - topic_hash: topic_hash.clone(), - message_ids: peer_message_ids, - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IHAVE"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(&peer_id); - } - // Increment failed message count - self.failed_messages - .entry(peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IHAVE, peer doesn't exist in connected peer list"); - } - } - } - } - - /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control - /// messages. - fn send_graft_prune( - &mut self, - to_graft: HashMap>, - mut to_prune: HashMap>, - no_px: HashSet, - ) { - // handle the grafts and overlapping prunes per peer - for (peer_id, topics) in to_graft.into_iter() { - for topic in &topics { - // inform scoring of graft - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(&peer_id, topic.clone()); - } - - // inform the handler of the peer being added to the mesh - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - peer_id, - vec![topic], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - // If there are prunes associated with the same peer add them. - // NOTE: In this case a peer has been added to a topic mesh, and removed from another. - // It therefore must be in at least one mesh and we do not need to inform the handler - // of its removal from another. - - // send the control messages - let mut sender = match self.connected_peers.get_mut(&peer_id) { - Some(connected_peer) => connected_peer.sender.clone(), - None => { - tracing::error!(peer_id = %peer_id, "Peer non-existent when sending graft/prune"); - return; - } - }; - - // The following prunes are not due to unsubscribing. - let prunes = to_prune - .remove(&peer_id) - .into_iter() - .flatten() - .map(|topic_hash| { - self.make_prune( - &topic_hash, - &peer_id, - self.config.do_px() && !no_px.contains(&peer_id), - false, - ) - }); - - for topic_hash in topics { - sender.graft(Graft { - topic_hash: topic_hash.clone(), - }); - } - - for prune in prunes { - sender.prune(prune); - } - } - - // handle the remaining prunes - // The following prunes are not due to unsubscribing. - for (peer_id, topics) in to_prune.iter() { - for topic_hash in topics { - let prune = self.make_prune( - topic_hash, - peer_id, - self.config.do_px() && !no_px.contains(peer_id), - false, - ); - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - peer.sender.prune(prune); - } else { - tracing::error!(peer = %peer_id, - "Could not send PRUNE, peer doesn't exist in connected peer list"); - } - - // inform the handler - peer_removed_from_mesh( - *peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - } - - /// Helper function which sends an IDONTWANT message to mesh\[topic\] peers. - fn send_idontwant( - &mut self, - message: &RawMessage, - msg_id: &MessageId, - propagation_source: Option<&PeerId>, - ) { - let Some(mesh_peers) = self.mesh.get(&message.topic) else { - return; - }; - - let iwant_peers = self.gossip_promises.peers_for_message(msg_id); - - let recipient_peers = mesh_peers - .iter() - .chain(iwant_peers.iter()) - .filter(|&peer_id| { - Some(peer_id) != propagation_source && Some(peer_id) != message.source.as_ref() - }); - - for peer_id in recipient_peers { - let Some(peer) = self.connected_peers.get_mut(peer_id) else { - // It can be the case that promises to disconnected peers appear here. In this case - // we simply ignore the peer-id. - continue; - }; - - // Only gossipsub 1.2 peers support IDONTWANT. - if peer.kind != PeerKind::Gossipsubv1_2 { - continue; - } - - if peer - .sender - .idontwant(IDontWant { - message_ids: vec![msg_id.clone()], - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IDONTWANT"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - return; - } - // IDONTWANT sent successfully. - if let Some(metrics) = self.metrics.as_mut() { - peer.dont_send_sent.insert(msg_id.clone(), Instant::now()); - // Don't exceed capacity. - if peer.dont_send_sent.len() > IDONTWANT_CAP { - peer.dont_send_sent.pop_front(); - } - metrics.register_idontwant_messages_sent_per_topic(&message.topic); - } - } - } - - /// Helper function which forwards a message to mesh\[topic\] peers. - /// - /// Returns true if at least one peer was messaged. - fn forward_msg( - &mut self, - msg_id: &MessageId, - message: RawMessage, - propagation_source: Option<&PeerId>, - originating_peers: HashSet, - ) -> Result { - // message is fully validated inform peer_score - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(peer) = propagation_source { - peer_score.deliver_message(peer, msg_id, &message.topic); - } - } - - tracing::debug!(message=%msg_id, "Forwarding message"); - let mut recipient_peers = HashSet::new(); - - // Populate the recipient peers mapping - - // Add explicit peers - for peer_id in &self.explicit_peers { - if let Some(peer) = self.connected_peers.get(peer_id) { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - && peer.topics.contains(&message.topic) - { - recipient_peers.insert(*peer_id); - } - } - } - - // add mesh peers - let topic = &message.topic; - // mesh - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - { - recipient_peers.insert(*peer_id); - } - } - } - - // forward the message to peers - if !recipient_peers.is_empty() { - for peer_id in recipient_peers.iter() { - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - if peer.dont_send_received.get(msg_id).is_some() { - tracing::debug!(%peer_id, message=%msg_id, "Peer doesn't want message"); - continue; - } - - tracing::debug!(%peer_id, message=%msg_id, "Sending message to peer"); - if peer - .sender - .forward( - message.clone(), - self.config.forward_queue_duration(), - self.metrics.as_mut(), - ) - .is_err() - { - // Downscore the peer - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment the failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not FORWARD, peer doesn't exist in connected peer list"); - } - } - tracing::debug!("Completed forwarding message"); - Ok(true) - } else { - Ok(false) - } - } - - /// Constructs a [`RawMessage`] performing message signing if required. - pub(crate) fn build_raw_message( - &mut self, - topic: TopicHash, - data: Vec, - ) -> Result { - match &mut self.publish_config { - PublishConfig::Signing { - ref keypair, - author, - inline_key, - last_seq_no, - } => { - let sequence_number = last_seq_no.next(); - - let signature = { - let message = proto::Message { - from: Some(author.to_bytes()), - data: Some(data.clone()), - seqno: Some(sequence_number.to_be_bytes().to_vec()), - topic: topic.clone().into_string(), - signature: None, - key: None, - }; - - let mut buf = Vec::with_capacity(message.get_size()); - let mut writer = Writer::new(&mut buf); - - message - .write_message(&mut writer) - .expect("Encoding to succeed"); - - // the signature is over the bytes "libp2p-pubsub:" - let mut signature_bytes = SIGNING_PREFIX.to_vec(); - signature_bytes.extend_from_slice(&buf); - Some(keypair.sign(&signature_bytes)?) - }; - - Ok(RawMessage { - source: Some(*author), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(sequence_number), - topic, - signature, - key: inline_key.clone(), - validated: true, // all published messages are valid - }) - } - PublishConfig::Author(peer_id) => { - Ok(RawMessage { - source: Some(*peer_id), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(rand::random()), - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - PublishConfig::RandomAuthor => { - Ok(RawMessage { - source: Some(PeerId::random()), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(rand::random()), - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - PublishConfig::Anonymous => { - Ok(RawMessage { - source: None, - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: None, - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - } - } - - fn on_connection_established( - &mut self, - ConnectionEstablished { - peer_id, - endpoint, - other_established, - .. - }: ConnectionEstablished, - ) { - // Diverging from the go implementation we only want to consider a peer as outbound peer - // if its first connection is outbound. - - if endpoint.is_dialer() && other_established == 0 && !self.px_peers.contains(&peer_id) { - // The first connection is outbound and it is not a peer from peer exchange => mark - // it as outbound peer - self.outbound_peers.insert(peer_id); - } - - // Add the IP to the peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint.get_remote_address()) { - peer_score.add_ip(&peer_id, ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint - ) - } - } - - if other_established > 0 { - return; // Not our first connection to this peer, hence nothing to do. - } - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.add_peer(peer_id); - } - - // Ignore connections from blacklisted peers. - if self.blacklisted_peers.contains(&peer_id) { - tracing::debug!(peer=%peer_id, "Ignoring connection from blacklisted peer"); - return; - } - - tracing::debug!(peer=%peer_id, "New peer connected"); - // We need to send our subscriptions to the newly-connected node. - if let Some(peer) = self.connected_peers.get_mut(&peer_id) { - for topic_hash in self.mesh.clone().into_keys() { - peer.sender.subscribe(topic_hash); - } - } else { - tracing::error!(peer = %peer_id, - "Could not send SUBSCRIBE, peer doesn't exist in connected peer list"); - } - } - - fn on_connection_closed( - &mut self, - ConnectionClosed { - peer_id, - connection_id, - endpoint, - remaining_established, - .. - }: ConnectionClosed, - ) { - // Remove IP from peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint.get_remote_address()) { - peer_score.remove_ip(&peer_id, &ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint - ) - } - } - - if remaining_established != 0 { - // Remove the connection from the list - if let Some(peer) = self.connected_peers.get_mut(&peer_id) { - let index = peer - .connections - .iter() - .position(|v| v == &connection_id) - .expect("Previously established connection to peer must be present"); - peer.connections.remove(index); - - // If there are more connections and this peer is in a mesh, inform the first connection - // handler. - if !peer.connections.is_empty() { - for topic in &peer.topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - self.events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(peer.connections[0]), - }); - break; - } - } - } - } - } - } else { - // remove from mesh, topic_peers, peer_topic and the fanout - tracing::debug!(peer=%peer_id, "Peer disconnected"); - - let Some(connected_peer) = self.connected_peers.get(&peer_id) else { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling disconnection"); - return; - }; - - // remove peer from all mappings - for topic in &connected_peer.topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if mesh_peers.remove(&peer_id) { - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic, Churn::Dc, 1); - m.set_mesh_peers(topic, mesh_peers.len()); - } - }; - } - - if let Some(m) = self.metrics.as_mut() { - m.dec_topic_peers(topic); - } - - // remove from fanout - self.fanout - .get_mut(topic) - .map(|peers| peers.remove(&peer_id)); - } - - // Forget px and outbound status for this peer - self.px_peers.remove(&peer_id); - self.outbound_peers.remove(&peer_id); - - // If metrics are enabled, register the disconnection of a peer based on its protocol. - if let Some(metrics) = self.metrics.as_mut() { - metrics.peer_protocol_disconnected(connected_peer.kind.clone()); - } - - self.connected_peers.remove(&peer_id); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.remove_peer(&peer_id); - } - } - } - - fn on_address_change( - &mut self, - AddressChange { - peer_id, - old: endpoint_old, - new: endpoint_new, - .. - }: AddressChange, - ) { - // Exchange IP in peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint_old.get_remote_address()) { - peer_score.remove_ip(&peer_id, &ip); - } else { - tracing::trace!( - peer=%&peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint_old - ) - } - if let Some(ip) = get_ip_addr(endpoint_new.get_remote_address()) { - peer_score.add_ip(&peer_id, ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint_new - ) - } - } - } -} - -fn get_ip_addr(addr: &Multiaddr) -> Option { - addr.iter().find_map(|p| match p { - Ip4(addr) => Some(IpAddr::V4(addr)), - Ip6(addr) => Some(IpAddr::V6(addr)), - _ => None, - }) -} - -impl NetworkBehaviour for Behaviour -where - C: Send + 'static + DataTransform, - F: Send + 'static + TopicSubscriptionFilter, -{ - type ConnectionHandler = Handler; - type ToSwarm = Event; - - fn handle_established_inbound_connection( - &mut self, - connection_id: ConnectionId, - peer_id: PeerId, - _: &Multiaddr, - _: &Multiaddr, - ) -> Result, ConnectionDenied> { - // By default we assume a peer is only a floodsub peer. - // - // The protocol negotiation occurs once a message is sent/received. Once this happens we - // update the type of peer that this is in order to determine which kind of routing should - // occur. - let connected_peer = self - .connected_peers - .entry(peer_id) - .or_insert(PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![], - sender: RpcSender::new(self.config.connection_handler_queue_len()), - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - }); - // Add the new connection - connected_peer.connections.push(connection_id); - - Ok(Handler::new( - self.config.protocol_config(), - connected_peer.sender.new_receiver(), - )) - } - - fn handle_established_outbound_connection( - &mut self, - connection_id: ConnectionId, - peer_id: PeerId, - _: &Multiaddr, - _: Endpoint, - _: PortUse, - ) -> Result, ConnectionDenied> { - // By default we assume a peer is only a floodsub peer. - // - // The protocol negotiation occurs once a message is sent/received. Once this happens we - // update the type of peer that this is in order to determine which kind of routing should - // occur. - let connected_peer = self - .connected_peers - .entry(peer_id) - .or_insert(PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![], - sender: RpcSender::new(self.config.connection_handler_queue_len()), - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - }); - // Add the new connection - connected_peer.connections.push(connection_id); - - Ok(Handler::new( - self.config.protocol_config(), - connected_peer.sender.new_receiver(), - )) - } - - fn on_connection_handler_event( - &mut self, - propagation_source: PeerId, - _connection_id: ConnectionId, - handler_event: THandlerOutEvent, - ) { - match handler_event { - HandlerEvent::PeerKind(kind) => { - // We have identified the protocol this peer is using - - if let Some(metrics) = self.metrics.as_mut() { - metrics.peer_protocol_connected(kind.clone()); - } - - if let PeerKind::NotSupported = kind { - tracing::debug!( - peer=%propagation_source, - "Peer does not support gossipsub protocols" - ); - self.events - .push_back(ToSwarm::GenerateEvent(Event::GossipsubNotSupported { - peer_id: propagation_source, - })); - } else if let Some(conn) = self.connected_peers.get_mut(&propagation_source) { - // Only change the value if the old value is Floodsub (the default set in - // `NetworkBehaviour::on_event` with FromSwarm::ConnectionEstablished). - // All other PeerKind changes are ignored. - tracing::debug!( - peer=%propagation_source, - peer_type=%kind, - "New peer type found for peer" - ); - if let PeerKind::Floodsub = conn.kind { - conn.kind = kind; - } - } - } - HandlerEvent::MessageDropped(rpc) => { - // Account for this in the scoring logic - if let Some((peer_score, _, _)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(&propagation_source); - } - - // Keep track of expired messages for the application layer. - match rpc { - RpcOut::Publish { .. } => { - self.failed_messages - .entry(propagation_source) - .or_default() - .publish += 1; - } - RpcOut::Forward { .. } => { - self.failed_messages - .entry(propagation_source) - .or_default() - .forward += 1; - } - _ => {} // - } - - // Record metrics on the failure. - if let Some(metrics) = self.metrics.as_mut() { - match rpc { - RpcOut::Publish { message, .. } => { - metrics.publish_msg_dropped(&message.topic); - } - RpcOut::Forward { message, .. } => { - metrics.forward_msg_dropped(&message.topic); - } - _ => {} - } - } - } - HandlerEvent::Message { - rpc, - invalid_messages, - } => { - // Handle the gossipsub RPC - - // Handle subscriptions - // Update connected peers topics - if !rpc.subscriptions.is_empty() { - self.handle_received_subscriptions(&rpc.subscriptions, &propagation_source); - } - - // Check if peer is graylisted in which case we ignore the event - if let (true, _) = - self.score_below_threshold(&propagation_source, |pst| pst.graylist_threshold) - { - tracing::debug!(peer=%propagation_source, "RPC Dropped from greylisted peer"); - return; - } - - // Handle any invalid messages from this peer - if self.peer_score.is_some() { - for (raw_message, validation_error) in invalid_messages { - self.handle_invalid_message( - &propagation_source, - &raw_message, - RejectReason::ValidationError(validation_error), - ) - } - } else { - // log the invalid messages - for (message, validation_error) in invalid_messages { - tracing::warn!( - peer=%propagation_source, - source=?message.source, - "Invalid message from peer. Reason: {:?}", - validation_error, - ); - } - } - - // Handle messages - for (count, raw_message) in rpc.messages.into_iter().enumerate() { - // Only process the amount of messages the configuration allows. - if self.config.max_messages_per_rpc().is_some() - && Some(count) >= self.config.max_messages_per_rpc() - { - tracing::warn!("Received more messages than permitted. Ignoring further messages. Processed: {}", count); - break; - } - self.handle_received_message(raw_message, &propagation_source); - } - - // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) - let mut ihave_msgs = vec![]; - let mut graft_msgs = vec![]; - let mut prune_msgs = vec![]; - for control_msg in rpc.control_msgs { - match control_msg { - ControlAction::IHave(IHave { - topic_hash, - message_ids, - }) => { - ihave_msgs.push((topic_hash, message_ids)); - } - ControlAction::IWant(IWant { message_ids }) => { - self.handle_iwant(&propagation_source, message_ids) - } - ControlAction::Graft(Graft { topic_hash }) => graft_msgs.push(topic_hash), - ControlAction::Prune(Prune { - topic_hash, - peers, - backoff, - }) => prune_msgs.push((topic_hash, peers, backoff)), - ControlAction::IDontWant(IDontWant { message_ids }) => { - let Some(peer) = self.connected_peers.get_mut(&propagation_source) - else { - tracing::error!(peer = %propagation_source, - "Could not handle IDONTWANT, peer doesn't exist in connected peer list"); - continue; - }; - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_idontwant(message_ids.len()); - let idontwant_size = message_ids.iter().map(|id| id.0.len()).sum(); - metrics.register_idontwant_bytes(idontwant_size); - } - for message_id in message_ids { - peer.dont_send_received.insert(message_id, Instant::now()); - // Don't exceed capacity. - if peer.dont_send_received.len() > IDONTWANT_CAP { - peer.dont_send_received.pop_front(); - } - } - } - } - } - if !ihave_msgs.is_empty() { - self.handle_ihave(&propagation_source, ihave_msgs); - } - if !graft_msgs.is_empty() { - self.handle_graft(&propagation_source, graft_msgs); - } - if !prune_msgs.is_empty() { - self.handle_prune(&propagation_source, prune_msgs); - } - } - } - } - - #[tracing::instrument(level = "trace", name = "NetworkBehaviour::poll", skip(self, cx))] - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - // update scores - if let Some((peer_score, _, delay)) = &mut self.peer_score { - if delay.poll_unpin(cx).is_ready() { - peer_score.refresh_scores(); - delay.reset(peer_score.params.decay_interval); - } - } - - if self.heartbeat.poll_unpin(cx).is_ready() { - self.heartbeat(); - self.heartbeat.reset(self.config.heartbeat_interval()); - } - - Poll::Pending - } - - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(connection_established) => { - self.on_connection_established(connection_established) - } - FromSwarm::ConnectionClosed(connection_closed) => { - self.on_connection_closed(connection_closed) - } - FromSwarm::AddressChange(address_change) => self.on_address_change(address_change), - _ => {} - } - } -} - -/// This is called when peers are added to any mesh. It checks if the peer existed -/// in any other mesh. If this is the first mesh they have joined, it queues a message to notify -/// the appropriate connection handler to maintain a connection. -fn peer_added_to_mesh( - peer_id: PeerId, - new_topics: Vec<&TopicHash>, - mesh: &HashMap>, - events: &mut VecDeque>, - connections: &HashMap, -) { - // Ensure there is an active connection - let connection_id = match connections.get(&peer_id) { - Some(p) => p - .connections - .first() - .expect("There should be at least one connection to a peer."), - None => { - tracing::error!(peer_id=%peer_id, "Peer not existent when added to the mesh"); - return; - } - }; - - if let Some(peer) = connections.get(&peer_id) { - for topic in &peer.topics { - if !new_topics.contains(&topic) { - if let Some(mesh_peers) = mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - // the peer is already in a mesh for another topic - return; - } - } - } - } - } - // This is the first mesh the peer has joined, inform the handler - events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(*connection_id), - }); -} - -/// This is called when peers are removed from a mesh. It checks if the peer exists -/// in any other mesh. If this is the last mesh they have joined, we return true, in order to -/// notify the handler to no longer maintain a connection. -fn peer_removed_from_mesh( - peer_id: PeerId, - old_topic: &TopicHash, - mesh: &HashMap>, - events: &mut VecDeque>, - connections: &HashMap, -) { - // Ensure there is an active connection - let connection_id = match connections.get(&peer_id) { - Some(p) => p - .connections - .first() - .expect("There should be at least one connection to a peer."), - None => { - tracing::error!(peer_id=%peer_id, "Peer not existent when removed from mesh"); - return; - } - }; - - if let Some(peer) = connections.get(&peer_id) { - for topic in &peer.topics { - if topic != old_topic { - if let Some(mesh_peers) = mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - // the peer exists in another mesh still - return; - } - } - } - } - } - // The peer is not in any other mesh, inform the handler - events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::LeftMesh, - handler: NotifyHandler::One(*connection_id), - }); -} - -/// Helper function to get a subset of random gossipsub peers for a `topic_hash` -/// filtered by the function `f`. The number of peers to get equals the output of `n_map` -/// that gets as input the number of filtered peers. -fn get_random_peers_dynamic( - connected_peers: &HashMap, - topic_hash: &TopicHash, - // maps the number of total peers to the number of selected peers - n_map: impl Fn(usize) -> usize, - mut f: impl FnMut(&PeerId) -> bool, -) -> BTreeSet { - let mut gossip_peers = connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(topic_hash)) - .filter(|(peer_id, _)| f(peer_id)) - .filter(|(_, p)| p.kind.is_gossipsub()) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - - // if we have less than needed, return them - let n = n_map(gossip_peers.len()); - if gossip_peers.len() <= n { - tracing::debug!("RANDOM PEERS: Got {:?} peers", gossip_peers.len()); - return gossip_peers.into_iter().collect(); - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = thread_rng(); - gossip_peers.partial_shuffle(&mut rng, n); - - tracing::debug!("RANDOM PEERS: Got {:?} peers", n); - - gossip_peers.into_iter().take(n).collect() -} - -/// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` -/// filtered by the function `f`. -fn get_random_peers( - connected_peers: &HashMap, - topic_hash: &TopicHash, - n: usize, - f: impl FnMut(&PeerId) -> bool, -) -> BTreeSet { - get_random_peers_dynamic(connected_peers, topic_hash, |_| n, f) -} - -/// Validates the combination of signing, privacy and message validation to ensure the -/// configuration will not reject published messages. -fn validate_config( - authenticity: &MessageAuthenticity, - validation_mode: &ValidationMode, -) -> Result<(), &'static str> { - match validation_mode { - ValidationMode::Anonymous => { - if authenticity.is_signing() { - return Err("Cannot enable message signing with an Anonymous validation mode. Consider changing either the ValidationMode or MessageAuthenticity"); - } - - if !authenticity.is_anonymous() { - return Err("Published messages contain an author but incoming messages with an author will be rejected. Consider adjusting the validation or privacy settings in the config"); - } - } - ValidationMode::Strict => { - if !authenticity.is_signing() { - return Err( - "Messages will be - published unsigned and incoming unsigned messages will be rejected. Consider adjusting - the validation or privacy settings in the config" - ); - } - } - _ => {} - } - Ok(()) -} - -impl fmt::Debug for Behaviour { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Behaviour") - .field("config", &self.config) - .field("events", &self.events.len()) - .field("publish_config", &self.publish_config) - .field("mesh", &self.mesh) - .field("fanout", &self.fanout) - .field("fanout_last_pub", &self.fanout_last_pub) - .field("mcache", &self.mcache) - .field("heartbeat", &self.heartbeat) - .finish() - } -} - -impl fmt::Debug for PublishConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PublishConfig::Signing { author, .. } => { - f.write_fmt(format_args!("PublishConfig::Signing({author})")) - } - PublishConfig::Author(author) => { - f.write_fmt(format_args!("PublishConfig::Author({author})")) - } - PublishConfig::RandomAuthor => f.write_fmt(format_args!("PublishConfig::RandomAuthor")), - PublishConfig::Anonymous => f.write_fmt(format_args!("PublishConfig::Anonymous")), - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs b/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs deleted file mode 100644 index 90b8fe43fb..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs +++ /dev/null @@ -1,5486 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// Collection of tests for the gossipsub network behaviour - -use super::*; -use crate::subscription_filter::WhitelistSubscriptionFilter; -use crate::types::RpcReceiver; -use crate::{config::ConfigBuilder, types::Rpc, IdentTopic as Topic}; -use byteorder::{BigEndian, ByteOrder}; -use futures::StreamExt; -use libp2p::core::ConnectedPoint; -use rand::Rng; -use std::net::Ipv4Addr; -use std::thread::sleep; - -#[derive(Default, Debug)] -struct InjectNodes { - peer_no: usize, - topics: Vec, - to_subscribe: bool, - gs_config: Config, - explicit: usize, - outbound: usize, - scoring: Option<(PeerScoreParams, PeerScoreThresholds)>, - data_transform: D, - subscription_filter: F, - peer_kind: Option, -} - -impl InjectNodes -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - #[allow(clippy::type_complexity)] - pub(crate) fn create_network( - self, - ) -> ( - Behaviour, - Vec, - HashMap, - Vec, - ) { - let keypair = libp2p::identity::Keypair::generate_ed25519(); - // create a gossipsub struct - let mut gs: Behaviour = Behaviour::new_with_subscription_filter_and_transform( - MessageAuthenticity::Signed(keypair), - self.gs_config, - None, - self.subscription_filter, - self.data_transform, - ) - .unwrap(); - - if let Some((scoring_params, scoring_thresholds)) = self.scoring { - gs.with_peer_score(scoring_params, scoring_thresholds) - .unwrap(); - } - - let mut topic_hashes = vec![]; - - // subscribe to the topics - for t in self.topics { - let topic = Topic::new(t); - gs.subscribe(&topic).unwrap(); - topic_hashes.push(topic.hash().clone()); - } - - // build and connect peer_no random peers - let mut peers = vec![]; - let mut receivers = HashMap::new(); - - let empty = vec![]; - for i in 0..self.peer_no { - let (peer, receiver) = add_peer_with_addr_and_kind( - &mut gs, - if self.to_subscribe { - &topic_hashes - } else { - &empty - }, - i < self.outbound, - i < self.explicit, - Multiaddr::empty(), - self.peer_kind.clone().or(Some(PeerKind::Gossipsubv1_1)), - ); - peers.push(peer); - receivers.insert(peer, receiver); - } - - (gs, peers, receivers, topic_hashes) - } - - fn peer_no(mut self, peer_no: usize) -> Self { - self.peer_no = peer_no; - self - } - - fn topics(mut self, topics: Vec) -> Self { - self.topics = topics; - self - } - - #[allow(clippy::wrong_self_convention)] - fn to_subscribe(mut self, to_subscribe: bool) -> Self { - self.to_subscribe = to_subscribe; - self - } - - fn gs_config(mut self, gs_config: Config) -> Self { - self.gs_config = gs_config; - self - } - - fn explicit(mut self, explicit: usize) -> Self { - self.explicit = explicit; - self - } - - fn outbound(mut self, outbound: usize) -> Self { - self.outbound = outbound; - self - } - - fn scoring(mut self, scoring: Option<(PeerScoreParams, PeerScoreThresholds)>) -> Self { - self.scoring = scoring; - self - } - - fn subscription_filter(mut self, subscription_filter: F) -> Self { - self.subscription_filter = subscription_filter; - self - } - - fn peer_kind(mut self, peer_kind: PeerKind) -> Self { - self.peer_kind = Some(peer_kind); - self - } -} - -fn inject_nodes() -> InjectNodes -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - InjectNodes::default() -} - -fn inject_nodes1() -> InjectNodes { - InjectNodes::::default() -} - -// helper functions for testing - -fn add_peer( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - add_peer_with_addr(gs, topic_hashes, outbound, explicit, Multiaddr::empty()) -} - -fn add_peer_with_addr( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, - address: Multiaddr, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - add_peer_with_addr_and_kind( - gs, - topic_hashes, - outbound, - explicit, - address, - Some(PeerKind::Gossipsubv1_1), - ) -} - -fn add_peer_with_addr_and_kind( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, - address: Multiaddr, - kind: Option, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - let peer = PeerId::random(); - let endpoint = if outbound { - ConnectedPoint::Dialer { - address, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - } - } else { - ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: address, - } - }; - - let sender = RpcSender::new(gs.config.connection_handler_queue_len()); - let receiver = sender.new_receiver(); - let connection_id = ConnectionId::new_unchecked(0); - gs.connected_peers.insert( - peer, - PeerConnections { - kind: kind.clone().unwrap_or(PeerKind::Floodsub), - connections: vec![connection_id], - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - sender, - }, - ); - - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: peer, - connection_id, - endpoint: &endpoint, - failed_addresses: &[], - other_established: 0, // first connection - })); - if let Some(kind) = kind { - gs.on_connection_handler_event( - peer, - ConnectionId::new_unchecked(0), - HandlerEvent::PeerKind(kind), - ); - } - if explicit { - gs.add_explicit_peer(&peer); - } - if !topic_hashes.is_empty() { - gs.handle_received_subscriptions( - &topic_hashes - .iter() - .cloned() - .map(|t| Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: t, - }) - .collect::>(), - &peer, - ); - } - (peer, receiver) -} - -fn disconnect_peer(gs: &mut Behaviour, peer_id: &PeerId) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - if let Some(peer_connections) = gs.connected_peers.get(peer_id) { - let fake_endpoint = ConnectedPoint::Dialer { - address: Multiaddr::empty(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }; // this is not relevant - // peer_connections.connections should never be empty. - - let mut active_connections = peer_connections.connections.len(); - for connection_id in peer_connections.connections.clone() { - active_connections = active_connections.checked_sub(1).unwrap(); - - gs.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id: *peer_id, - connection_id, - endpoint: &fake_endpoint, - remaining_established: active_connections, - cause: None, - })); - } - } -} - -// Converts a protobuf message into a gossipsub message for reading the Gossipsub event queue. -fn proto_to_message(rpc: &proto::RPC) -> Rpc { - // Store valid messages. - let mut messages = Vec::with_capacity(rpc.publish.len()); - let rpc = rpc.clone(); - for message in rpc.publish.into_iter() { - messages.push(RawMessage { - source: message.from.map(|x| PeerId::from_bytes(&x).unwrap()), - data: message.data.unwrap_or_default(), - sequence_number: message.seqno.map(|x| BigEndian::read_u64(&x)), // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: None, - validated: false, - }); - } - let mut control_msgs = Vec::new(); - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| { - ControlAction::IHave(IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| { - ControlAction::IWant(IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| { - ControlAction::Graft(Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - }) - .collect(); - - let mut prune_msgs = Vec::new(); - - for prune in rpc_control.prune { - // filter out invalid peers - let peers = prune - .peers - .into_iter() - .filter_map(|info| { - info.peer_id - .and_then(|id| PeerId::from_bytes(&id).ok()) - .map(|peer_id| - //TODO signedPeerRecord, see https://github.com/libp2p/specs/pull/217 - PeerInfo { - peer_id: Some(peer_id), - }) - }) - .collect::>(); - - let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune(Prune { - topic_hash, - peers, - backoff: prune.backoff, - })); - } - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - } - - Rpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| Subscription { - action: if Some(true) == sub.subscribe { - SubscriptionAction::Subscribe - } else { - SubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - } -} - -#[test] -/// Test local node subscribing to a topic -fn test_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(subscribe_topic) - .to_subscribe(true) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // collect all the subscriptions - let subscriptions = receivers - .into_values() - .fold(0, |mut collected_subscriptions, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { - collected_subscriptions += 1 - } - } - collected_subscriptions - }); - - // we sent a subscribe to all known peers - assert_eq!(subscriptions, 20); -} - -/// Test unsubscribe. -#[test] -fn test_unsubscribe() { - // Unsubscribe should: - // - Remove the mesh entry for topic - // - Send UNSUBSCRIBE to all known peers - // - Call Leave - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - // subscribe to topic_strings - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topic_strings) - .to_subscribe(true) - .create_network(); - - for topic_hash in &topic_hashes { - assert!( - gs.connected_peers - .values() - .any(|p| p.topics.contains(topic_hash)), - "Topic_peers contain a topic entry" - ); - assert!( - gs.mesh.contains_key(topic_hash), - "mesh should contain a topic entry" - ); - } - - // unsubscribe from both topics - assert!( - gs.unsubscribe(&topics[0]).unwrap(), - "should be able to unsubscribe successfully from each topic", - ); - assert!( - gs.unsubscribe(&topics[1]).unwrap(), - "should be able to unsubscribe successfully from each topic", - ); - - // collect all the subscriptions - let subscriptions = receivers - .into_values() - .fold(0, |mut collected_subscriptions, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { - collected_subscriptions += 1 - } - } - collected_subscriptions - }); - - // we sent a unsubscribe to all known peers, for two topics - assert_eq!(subscriptions, 40); - - // check we clean up internal structures - for topic_hash in &topic_hashes { - assert!( - !gs.mesh.contains_key(topic_hash), - "All topics should have been removed from the mesh" - ); - } -} - -/// Test JOIN(topic) functionality. -#[test] -fn test_join() { - // The Join function should: - // - Remove peers from fanout[topic] - // - Add any fanout[topic] peers to the mesh (up to mesh_n) - // - Fill up to mesh_n peers from known gossipsub peers in the topic - // - Send GRAFT messages to all nodes added to the mesh - - // This test is not an isolated unit test, rather it uses higher level, - // subscribe/unsubscribe to perform the test. - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - let (mut gs, _, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topic_strings) - .to_subscribe(true) - .create_network(); - - // Flush previous GRAFT messages. - receivers = flush_events(&mut gs, receivers); - - // unsubscribe, then call join to invoke functionality - assert!( - gs.unsubscribe(&topics[0]).unwrap(), - "should be able to unsubscribe successfully" - ); - assert!( - gs.unsubscribe(&topics[1]).unwrap(), - "should be able to unsubscribe successfully" - ); - - // re-subscribe - there should be peers associated with the topic - assert!( - gs.subscribe(&topics[0]).unwrap(), - "should be able to subscribe successfully" - ); - - // should have added mesh_n nodes to the mesh - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - - fn count_grafts( - receivers: HashMap, - ) -> (usize, HashMap) { - let mut new_receivers = HashMap::new(); - let mut acc = 0; - - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Graft(_)) = priority.try_recv() { - acc += 1; - } - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: c.non_priority, - }, - ); - } - (acc, new_receivers) - } - - // there should be mesh_n GRAFT messages. - let (graft_messages, mut receivers) = count_grafts(receivers); - - assert_eq!( - graft_messages, 6, - "There should be 6 grafts messages sent to peers" - ); - - // verify fanout nodes - // add 3 random peers to the fanout[topic1] - gs.fanout - .insert(topic_hashes[1].clone(), Default::default()); - let mut new_peers: Vec = vec![]; - - for _ in 0..3 { - let random_peer = PeerId::random(); - // inform the behaviour of a new peer - let address = "/ip4/127.0.0.1".parse::().unwrap(); - gs.handle_established_inbound_connection( - ConnectionId::new_unchecked(0), - random_peer, - &address, - &address, - ) - .unwrap(); - let sender = RpcSender::new(gs.config.connection_handler_queue_len()); - let receiver = sender.new_receiver(); - let connection_id = ConnectionId::new_unchecked(0); - gs.connected_peers.insert( - random_peer, - PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![connection_id], - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - sender, - }, - ); - receivers.insert(random_peer, receiver); - - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: random_peer, - connection_id, - endpoint: &ConnectedPoint::Dialer { - address, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 0, - })); - - // add the new peer to the fanout - let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap(); - fanout_peers.insert(random_peer); - new_peers.push(random_peer); - } - - // subscribe to topic1 - gs.subscribe(&topics[1]).unwrap(); - - // the three new peers should have been added, along with 3 more from the pool. - assert!( - gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap(); - for new_peer in new_peers { - assert!( - mesh_peers.contains(&new_peer), - "Fanout peer should be included in the mesh" - ); - } - - // there should now 6 graft messages to be sent - let (graft_messages, _) = count_grafts(receivers); - - assert_eq!( - graft_messages, 6, - "There should be 6 grafts messages sent to peers" - ); -} - -/// Test local node publish to subscribed topic -#[test] -fn test_publish_without_flood_publishing() { - // node should: - // - Send publish message to all peers - // - Insert message into gs.mcache and gs.received - - //turn off flood publish to test old behaviour - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - - let publish_topic = String::from("test_publish"); - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![publish_topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // all peers should be subscribed to the topic - assert_eq!( - gs.connected_peers - .values() - .filter(|p| p.topics.contains(&topic_hashes[0])) - .count(), - 20, - "Peers should be subscribed to the topic" - ); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new(publish_topic), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - let config: Config = Config::default(); - assert_eq!( - publishes.len(), - config.mesh_n(), - "Should send a publish message to at least mesh_n peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -/// Test local node publish to unsubscribed topic -#[test] -fn test_fanout() { - // node should: - // - Populate fanout peers - // - Send publish message to fanout peers - // - Insert message into gs.mcache and gs.received - - //turn off flood publish to test fanout behaviour - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - - let fanout_topic = String::from("test_fanout"); - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![fanout_topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - // Unsubscribe from topic - assert!( - gs.unsubscribe(&Topic::new(fanout_topic.clone())).unwrap(), - "should be able to unsubscribe successfully from topic" - ); - - // Publish on unsubscribed topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new(fanout_topic.clone()), publish_data) - .unwrap(); - - assert_eq!( - gs.fanout - .get(&TopicHash::from_raw(fanout_topic)) - .unwrap() - .len(), - gs.config.mesh_n(), - "Fanout should contain `mesh_n` peers for fanout topic" - ); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - assert_eq!( - publishes.len(), - gs.config.mesh_n(), - "Should send a publish message to `mesh_n` fanout peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -/// Test the gossipsub NetworkBehaviour peer connection logic. -#[test] -fn test_inject_connected() { - let (gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .create_network(); - - // check that our subscriptions are sent to each of the peers - // collect all the SendEvents - let subscriptions = receivers.into_iter().fold( - HashMap::>::new(), - |mut collected_subscriptions, (peer, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(topic)) = priority.try_recv() { - let mut peer_subs = collected_subscriptions.remove(&peer).unwrap_or_default(); - peer_subs.push(topic.into_string()); - collected_subscriptions.insert(peer, peer_subs); - } - } - collected_subscriptions - }, - ); - - // check that there are two subscriptions sent to each peer - for peer_subs in subscriptions.values() { - assert!(peer_subs.contains(&String::from("topic1"))); - assert!(peer_subs.contains(&String::from("topic2"))); - assert_eq!(peer_subs.len(), 2); - } - - // check that there are 20 send events created - assert_eq!(subscriptions.len(), 20); - - // should add the new peers to `peer_topics` with an empty vec as a gossipsub node - for peer in peers { - let peer = gs.connected_peers.get(&peer).unwrap(); - assert!( - peer.topics == topic_hashes.iter().cloned().collect(), - "The topics for each node should all topics" - ); - } -} - -/// Test subscription handling -#[test] -fn test_handle_received_subscriptions() { - // For every subscription: - // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. - // - Add peer to topics_peer. - // UNSUBSCRIBE - Remove topic from peer_topics for peer. - // - Remove peer from topic_peers. - - let topics = ["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - let (mut gs, peers, _receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topics) - .to_subscribe(false) - .create_network(); - - // The first peer sends 3 subscriptions and 1 unsubscription - let mut subscriptions = topic_hashes[..3] - .iter() - .map(|topic_hash| Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }) - .collect::>(); - - subscriptions.push(Subscription { - action: SubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[topic_hashes.len() - 1].clone(), - }); - - let unknown_peer = PeerId::random(); - // process the subscriptions - // first and second peers send subscriptions - gs.handle_received_subscriptions(&subscriptions, &peers[0]); - gs.handle_received_subscriptions(&subscriptions, &peers[1]); - // unknown peer sends the same subscriptions - gs.handle_received_subscriptions(&subscriptions, &unknown_peer); - - // verify the result - - let peer = gs.connected_peers.get(&peers[0]).unwrap(); - assert!( - peer.topics - == topic_hashes - .iter() - .take(3) - .cloned() - .collect::>(), - "First peer should be subscribed to three topics" - ); - let peer1 = gs.connected_peers.get(&peers[1]).unwrap(); - assert!( - peer1.topics - == topic_hashes - .iter() - .take(3) - .cloned() - .collect::>(), - "Second peer should be subscribed to three topics" - ); - - assert!( - !gs.connected_peers.contains_key(&unknown_peer), - "Unknown peer should not have been added" - ); - - for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(topic_hash)) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - assert!( - topic_peers == peers[..2].iter().cloned().collect(), - "Two peers should be added to the first three topics" - ); - } - - // Peer 0 unsubscribes from the first topic - - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[0].clone(), - }], - &peers[0], - ); - - let peer = gs.connected_peers.get(&peers[0]).unwrap(); - assert!( - peer.topics == topic_hashes[1..3].iter().cloned().collect::>(), - "Peer should be subscribed to two topics" - ); - - // only gossipsub at the moment - let topic_peers = gs - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(&topic_hashes[0])) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - - assert!( - topic_peers == peers[1..2].iter().cloned().collect(), - "Only the second peers should be in the first topic" - ); -} - -/// Test Gossipsub.get_random_peers() function -#[test] -fn test_get_random_peers() { - // generate a default Config - let gs_config = ConfigBuilder::default() - .validation_mode(ValidationMode::Anonymous) - .build() - .unwrap(); - // create a gossipsub struct - let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::Anonymous, gs_config).unwrap(); - - // create a topic and fill it with some peers - let topic_hash = Topic::new("Test").hash(); - let mut peers = vec![]; - let mut topics = BTreeSet::new(); - topics.insert(topic_hash.clone()); - - for _ in 0..20 { - let peer_id = PeerId::random(); - peers.push(peer_id); - gs.connected_peers.insert( - peer_id, - PeerConnections { - kind: PeerKind::Gossipsubv1_1, - connections: vec![ConnectionId::new_unchecked(0)], - topics: topics.clone(), - sender: RpcSender::new(gs.config.connection_handler_queue_len()), - dont_send_sent: LinkedHashMap::new(), - dont_send_received: LinkedHashMap::new(), - }, - ); - } - - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| true); - assert_eq!(random_peers.len(), 5, "Expected 5 peers to be returned"); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 30, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!( - random_peers == peers.iter().cloned().collect(), - "Expected no shuffling" - ); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 20, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!( - random_peers == peers.iter().cloned().collect(), - "Expected no shuffling" - ); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 0, |_| true); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - // test the filter - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| false); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 10, { - |peer| peers.contains(peer) - }); - assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); -} - -/// Tests that the correct message is sent when a peer asks for a message in our cache. -#[test] -fn test_handle_iwant_msg_cached() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - let raw_message = RawMessage { - source: Some(peers[11]), - data: vec![1, 2, 3, 4], - sequence_number: Some(1u64), - topic: TopicHash::from_raw("topic"), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - let msg_id = gs.config.message_id(message); - gs.mcache.put(&msg_id, raw_message); - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = receivers - .into_values() - .fold(vec![], |mut collected_messages, c| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { - collected_messages.push(message) - } - } - collected_messages - }); - - assert!( - sent_messages - .iter() - .map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap()) - .any(|msg| gs.config.message_id(&msg) == msg_id), - "Expected the cached message to be sent to an IWANT peer" - ); -} - -/// Tests that messages are sent correctly depending on the shifting of the message cache. -#[test] -fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, mut receivers, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - // perform 10 memshifts and check that it leaves the cache - for shift in 1..10 { - let raw_message = RawMessage { - source: Some(peers[11]), - data: vec![1, 2, 3, 4], - sequence_number: Some(shift), - topic: TopicHash::from_raw("topic"), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - let msg_id = gs.config.message_id(message); - gs.mcache.put(&msg_id, raw_message); - for _ in 0..shift { - gs.mcache.shift(); - } - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // is the message is being sent? - let mut message_exists = false; - receivers = receivers.into_iter().map(|(peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message, timeout: _ }) if - gs.config.message_id( - &gs.data_transform - .inbound_transform(message.clone()) - .unwrap(), - ) == msg_id) - { - message_exists = true; - } - } - ( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: c.priority, - non_priority: non_priority.peekable(), - }, - ) - }).collect(); - // default history_length is 5, expect no messages after shift > 5 - if shift < 5 { - assert!( - message_exists, - "Expected the cached message to be sent to an IWANT peer before 5 shifts" - ); - } else { - assert!( - !message_exists, - "Expected the cached message to not be sent to an IWANT peer after 5 shifts" - ); - } - } -} - -/// tests that an event is not created when a peers asks for a message not in our cache -#[test] -fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - let events_before = gs.events.len(); - gs.handle_iwant(&peers[7], vec![MessageId::new(b"unknown id")]); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ); -} - -/// tests that an event is created when a peer shares that it has a message we want -#[test] -fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_ihave( - &peers[7], - vec![(topic_hashes[0].clone(), vec![MessageId::new(b"unknown id")])], - ); - - // check that we sent an IWANT request for `unknown id` - let mut iwant_exists = false; - let receiver = receivers.remove(&peers[7]).unwrap(); - let non_priority = receiver.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IWant(IWant { message_ids })) = non_priority.try_recv() { - if message_ids - .iter() - .any(|m| *m == MessageId::new(b"unknown id")) - { - iwant_exists = true; - break; - } - } - } - - assert!( - iwant_exists, - "Expected to send an IWANT control message for unkown message id" - ); -} - -/// tests that an event is not created when a peer shares that it has a message that -/// we already have -#[test] -fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - let msg_id = MessageId::new(b"known id"); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ) -} - -/// test that an event is not created when a peer shares that it has a message in -/// a topic that we are not subscribed to -#[test] -fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(20) - .topics(vec![]) - .to_subscribe(true) - .create_network(); - - let events_before = gs.events.len(); - gs.handle_ihave( - &peers[7], - vec![( - TopicHash::from_raw(String::from("unsubscribed topic")), - vec![MessageId::new(b"irrelevant id")], - )], - ); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ) -} - -/// tests that a peer is added to our mesh when we are both subscribed -/// to the same topic -#[test] -fn test_handle_graft_is_subscribed() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_graft(&peers[7], topic_hashes.clone()); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); -} - -/// tests that a peer is not added to our mesh when they are subscribed to -/// a topic that we are not -#[test] -fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_graft( - &peers[7], - vec![TopicHash::from_raw(String::from("unsubscribed topic"))], - ); - - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); -} - -/// tests multiple topics in a single graft message -#[test] -fn test_handle_graft_multiple_topics() { - let topics: Vec = ["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topics) - .to_subscribe(true) - .create_network(); - - let mut their_topics = topic_hashes.clone(); - // their_topics = [topic1, topic2, topic3] - // our_topics = [topic1, topic2, topic4] - their_topics.pop(); - gs.leave(&their_topics[2]); - - gs.handle_graft(&peers[7], their_topics.clone()); - - for hash in topic_hashes.iter().take(2) { - assert!( - gs.mesh.get(hash).unwrap().contains(&peers[7]), - "Expected peer to be in the mesh for the first 2 topics" - ); - } - - assert!( - !gs.mesh.contains_key(&topic_hashes[2]), - "Expected the second topic to not be in the mesh" - ); -} - -/// tests that a peer is removed from our mesh -#[test] -fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - // insert peer into our mesh for 'topic1' - gs.mesh - .insert(topic_hashes[0].clone(), peers.iter().cloned().collect()); - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be in mesh" - ); - - gs.handle_prune( - &peers[7], - topic_hashes - .iter() - .map(|h| (h.clone(), vec![], None)) - .collect(), - ); - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be removed from mesh" - ); -} - -fn count_control_msgs( - receivers: HashMap, - mut filter: impl FnMut(&PeerId, &RpcOut) -> bool, -) -> (usize, HashMap) { - let mut new_receivers = HashMap::new(); - let mut collected_messages = 0; - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - let non_priority = c.non_priority.into_inner(); - while !priority.is_empty() || !non_priority.is_empty() { - if let Ok(rpc) = priority.try_recv() { - if filter(&peer_id, &rpc) { - collected_messages += 1; - } - } - if let Ok(rpc) = non_priority.try_recv() { - if filter(&peer_id, &rpc) { - collected_messages += 1; - } - } - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: non_priority.peekable(), - }, - ); - } - (collected_messages, new_receivers) -} - -fn flush_events( - gs: &mut Behaviour, - receivers: HashMap, -) -> HashMap { - gs.events.clear(); - let mut new_receivers = HashMap::new(); - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - let non_priority = c.non_priority.into_inner(); - while !priority.is_empty() || !non_priority.is_empty() { - let _ = priority.try_recv(); - let _ = non_priority.try_recv(); - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: non_priority.peekable(), - }, - ); - } - new_receivers -} - -/// tests that a peer added as explicit peer gets connected to -#[test] -fn test_explicit_peer_gets_connected() { - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - //create new peer - let peer = PeerId::random(); - - //add peer as explicit peer - gs.add_explicit_peer(&peer); - - let num_events = gs - .events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(peer), - _ => false, - }) - .count(); - - assert_eq!( - num_events, 1, - "There was no dial peer event for the explicit peer" - ); -} - -#[test] -fn test_explicit_peer_reconnects() { - let config = ConfigBuilder::default() - .check_explicit_peers_ticks(2) - .build() - .unwrap(); - let (mut gs, others, receivers, _) = inject_nodes1() - .peer_no(1) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - let peer = others.first().unwrap(); - - //add peer as explicit peer - gs.add_explicit_peer(peer); - - flush_events(&mut gs, receivers); - - //disconnect peer - disconnect_peer(&mut gs, peer); - - gs.heartbeat(); - - //check that no reconnect after first heartbeat since `explicit_peer_ticks == 2` - assert_eq!( - gs.events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(*peer), - _ => false, - }) - .count(), - 0, - "There was a dial peer event before explicit_peer_ticks heartbeats" - ); - - gs.heartbeat(); - - //check that there is a reconnect after second heartbeat - assert!( - gs.events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(*peer), - _ => false, - }) - .count() - >= 1, - "There was no dial peer event for the explicit peer" - ); -} - -#[test] -fn test_handle_graft_explicit_peer() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let peer = peers.first().unwrap(); - - gs.handle_graft(peer, topic_hashes.clone()); - - //peer got not added to mesh - assert!(gs.mesh[&topic_hashes[0]].is_empty()); - assert!(gs.mesh[&topic_hashes[1]].is_empty()); - - //check prunes - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == peer - && match m { - RpcOut::Prune(Prune { topic_hash, .. }) => { - topic_hash == &topic_hashes[0] || topic_hash == &topic_hashes[1] - } - _ => false, - } - }); - assert!( - control_msgs >= 2, - "Not enough prunes sent when grafting from explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_on_receiving_subscription() { - let (gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!( - gs.mesh[&topic_hashes[0]], - vec![peers[1]].into_iter().collect() - ); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs >= 1, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn do_not_graft_explicit_peer() { - let (mut gs, others, receivers, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(vec![String::from("topic")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - gs.heartbeat(); - - //mesh stays empty - assert_eq!(gs.mesh[&topic_hashes[0]], BTreeSet::new()); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &others[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn do_forward_messages_to_explicit_peers() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message: m, timeout: _}) if peer_id == peers[0] && m.data == message.data) { - fwds +=1; - } - } - fwds - }), - 1, - "The message did not get forwarded to the explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_on_subscribe() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(2) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //create new topic, both peers subscribing to it but we do not subscribe to it - let topic = Topic::new(String::from("t")); - let topic_hash = topic.hash(); - for peer in peers.iter().take(2) { - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }], - peer, - ); - } - - //subscribe now to topic - gs.subscribe(&topic).unwrap(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs > 0, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(2) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //create new topic, both peers subscribing to it but we do not subscribe to it - let topic = Topic::new(String::from("t")); - let topic_hash = topic.hash(); - for peer in peers.iter().take(2) { - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }], - peer, - ); - } - - //we send a message for this topic => this will initialize the fanout - gs.publish(topic.clone(), vec![1, 2, 3]).unwrap(); - - //subscribe now to topic - gs.subscribe(&topic).unwrap(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs >= 1, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn no_gossip_gets_sent_to_explicit_peers() { - let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - //forward the message - gs.handle_received_message(message, &local_id); - - //simulate multiple gossip calls (for randomness) - for _ in 0..3 { - gs.emit_gossip(); - } - - //assert that no gossip gets sent to explicit peer - let receiver = receivers.remove(&peers[0]).unwrap(); - let mut gossips = 0; - let non_priority = receiver.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IHave(_)) = non_priority.try_recv() { - gossips += 1; - } - } - assert_eq!(gossips, 0, "Gossip got emitted to explicit peer"); -} - -/// Tests the mesh maintenance addition -#[test] -fn test_mesh_addition() { - let config: Config = Config::default(); - - // Adds mesh_low peers and PRUNE 2 giving us a deficit. - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - let to_remove_peers = config.mesh_n() + 1 - config.mesh_n_low() - 1; - - for peer in peers.iter().take(to_remove_peers) { - gs.handle_prune( - peer, - topics.iter().map(|h| (h.clone(), vec![], None)).collect(), - ); - } - - // Verify the pruned peers are removed from the mesh. - assert_eq!( - gs.mesh.get(&topics[0]).unwrap().len(), - config.mesh_n_low() - 1 - ); - - // run a heartbeat - gs.heartbeat(); - - // Peers should be added to reach mesh_n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n()); -} - -/// Tests the mesh maintenance subtraction -#[test] -fn test_mesh_subtraction() { - let config = Config::default(); - - // Adds mesh_low peers and PRUNE 2 giving us a deficit. - let n = config.mesh_n_high() + 10; - //make all outbound connections so that we allow grafting to all - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .outbound(n) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // run a heartbeat - gs.heartbeat(); - - // Peers should be removed to reach mesh_n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n()); -} - -#[test] -fn test_connect_to_px_peers_on_handle_prune() { - let config: Config = Config::default(); - - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //handle prune from single peer with px peers - - let mut px = Vec::new(); - //propose more px peers than config.prune_peers() - for _ in 0..config.prune_peers() + 5 { - px.push(PeerInfo { - peer_id: Some(PeerId::random()), - }); - } - - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px.clone(), - Some(config.prune_backoff().as_secs()), - )], - ); - - //Check DialPeer events for px peers - let dials: Vec<_> = gs - .events - .iter() - .filter_map(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id(), - _ => None, - }) - .collect(); - - // Exactly config.prune_peers() many random peers should be dialled - assert_eq!(dials.len(), config.prune_peers()); - - let dials_set: HashSet<_> = dials.into_iter().collect(); - - // No duplicates - assert_eq!(dials_set.len(), config.prune_peers()); - - //all dial peers must be in px - assert!(dials_set.is_subset( - &px.iter() - .map(|i| *i.peer_id.as_ref().unwrap()) - .collect::>() - )); -} - -#[test] -fn test_send_px_and_backoff_in_prune() { - let config: Config = Config::default(); - - //build mesh with enough peers for px - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(config.prune_peers() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //send prune to peer - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - peers.len() == config.prune_peers() && - //all peers are different - peers.iter().collect::>().len() == - config.prune_peers() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_prune_backoffed_peer_on_graft() { - let config: Config = Config::default(); - - //build mesh with enough peers for px - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(config.prune_peers() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //remove peer from mesh and send prune to peer => this adds a backoff for this peer - gs.mesh.get_mut(&topics[0]).unwrap().remove(&peers[0]); - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - //ignore all messages until now - let receivers = flush_events(&mut gs, receivers); - - //handle graft - gs.handle_graft(&peers[0], vec![topics[0].clone()]); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - //no px in this case - peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_graft_within_backoff_period() { - let config = ConfigBuilder::default() - .backoff_slack(1) - .heartbeat_interval(Duration::from_millis(100)) - .build() - .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - //handle prune from peer with backoff of one second - gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), Some(1))]); - - //forget all events until now - let receivers = flush_events(&mut gs, receivers); - - //call heartbeat - gs.heartbeat(); - - //Sleep for one second and apply 10 regular heartbeats (interval = 100ms). - for _ in 0..10 { - sleep(Duration::from_millis(100)); - gs.heartbeat(); - } - - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - //Heartbeat one more time this should graft now - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_do_not_graft_within_default_backoff_period_after_receiving_prune_without_backoff() { - //set default backoff period to 1 second - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(90)) - .backoff_slack(1) - .heartbeat_interval(Duration::from_millis(100)) - .build() - .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - //handle prune from peer without a specified backoff - gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), None)]); - - //forget all events until now - let receivers = flush_events(&mut gs, receivers); - - //call heartbeat - gs.heartbeat(); - - //Apply one more heartbeat - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - //Heartbeat one more time this should graft now - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_unsubscribe_backoff() { - const HEARTBEAT_INTERVAL: Duration = Duration::from_millis(100); - let config = ConfigBuilder::default() - .backoff_slack(1) - // ensure a prune_backoff > unsubscribe_backoff - .prune_backoff(Duration::from_secs(5)) - .unsubscribe_backoff(1) - .heartbeat_interval(HEARTBEAT_INTERVAL) - .build() - .unwrap(); - - let topic = String::from("test"); - // only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, _, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec![topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - let _ = gs.unsubscribe(&Topic::new(topic)); - - let (control_msgs, receivers) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { backoff, .. }) => backoff == &Some(1), - _ => false, - }); - assert_eq!( - control_msgs, 1, - "Peer should be pruned with `unsubscribe_backoff`." - ); - - let _ = gs.subscribe(&Topic::new(topics[0].to_string())); - - // forget all events until now - let receivers = flush_events(&mut gs, receivers); - - // call heartbeat - gs.heartbeat(); - - // Sleep for one second and apply 10 regular heartbeats (interval = 100ms). - for _ in 0..10 { - sleep(HEARTBEAT_INTERVAL); - gs.heartbeat(); - } - - // Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - // Heartbeat one more time this should graft now - sleep(HEARTBEAT_INTERVAL); - gs.heartbeat(); - - // check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_flood_publish() { - let config: Config = Config::default(); - - let topic = "test"; - // Adds more peers than mesh can hold to test flood publishing - let (mut gs, _, receivers, _) = inject_nodes1() - .peer_no(config.mesh_n_high() + 10) - .topics(vec![topic.into()]) - .to_subscribe(true) - .create_network(); - - //publish message - let publish_data = vec![0; 42]; - gs.publish(Topic::new(topic), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - let config: Config = Config::default(); - assert_eq!( - publishes.len(), - config.mesh_n_high() + 10, - "Should send a publish message to all known peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -#[test] -fn test_gossip_to_at_least_gossip_lazy_peers() { - let config: Config = Config::default(); - - //add more peers than in mesh to test gossipping - //by default only mesh_n_low peers will get added to mesh - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(config.mesh_n_low() + config.gossip_lazy() + 1) - .topics(vec!["topic".into()]) - .to_subscribe(true) - .create_network(); - - //receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - //emit gossip - gs.emit_gossip(); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - //check that exactly config.gossip_lazy() many gossip messages were sent. - let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }); - assert_eq!(control_msgs, config.gossip_lazy()); -} - -#[test] -fn test_gossip_to_at_most_gossip_factor_peers() { - let config: Config = Config::default(); - - //add a lot of peers - let m = config.mesh_n_low() + config.gossip_lazy() * (2.0 / config.gossip_factor()) as usize; - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(m) - .topics(vec!["topic".into()]) - .to_subscribe(true) - .create_network(); - - //receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - //emit gossip - gs.emit_gossip(); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - //check that exactly config.gossip_lazy() many gossip messages were sent. - let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }); - assert_eq!( - control_msgs, - ((m - config.mesh_n_low()) as f64 * config.gossip_factor()) as usize - ); -} - -#[test] -fn test_accept_only_outbound_peer_grafts_when_mesh_full() { - let config: Config = Config::default(); - - //enough peers to fill the mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - // graft all the peers => this will fill the mesh - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //assert current mesh size - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high()); - - //create an outbound and an inbound peer - let (inbound, _in_reciver) = add_peer(&mut gs, &topics, false, false); - let (outbound, _out_receiver) = add_peer(&mut gs, &topics, true, false); - - //send grafts - gs.handle_graft(&inbound, vec![topics[0].clone()]); - gs.handle_graft(&outbound, vec![topics[0].clone()]); - - //assert mesh size - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high() + 1); - - //inbound is not in mesh - assert!(!gs.mesh[&topics[0]].contains(&inbound)); - - //outbound is in mesh - assert!(gs.mesh[&topics[0]].contains(&outbound)); -} - -#[test] -fn test_do_not_remove_too_many_outbound_peers() { - //use an extreme case to catch errors with high probability - let m = 50; - let n = 2 * m; - let config = ConfigBuilder::default() - .mesh_n_high(n) - .mesh_n(n) - .mesh_n_low(n) - .mesh_outbound_min(m) - .build() - .unwrap(); - - //fill the mesh with inbound connections - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //create m outbound connections and graft (we will accept the graft) - let mut outbound = HashSet::new(); - for _ in 0..m { - let (peer, _) = add_peer(&mut gs, &topics, true, false); - outbound.insert(peer); - gs.handle_graft(&peer, topics.clone()); - } - - //mesh is overly full - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n + m); - - // run a heartbeat - gs.heartbeat(); - - // Peers should be removed to reach n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n); - - //all outbound peers are still in the mesh - assert!(outbound.iter().all(|p| gs.mesh[&topics[0]].contains(p))); -} - -#[test] -fn test_add_outbound_peers_if_min_is_not_satisfied() { - let config: Config = Config::default(); - - // Fill full mesh with inbound peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //create config.mesh_outbound_min() many outbound connections without grafting - let mut peers = vec![]; - for _ in 0..config.mesh_outbound_min() { - peers.push(add_peer(&mut gs, &topics, true, false)); - } - - // Nothing changed in the mesh yet - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high()); - - // run a heartbeat - gs.heartbeat(); - - // The outbound peers got additionally added - assert_eq!( - gs.mesh[&topics[0]].len(), - config.mesh_n_high() + config.mesh_outbound_min() - ); -} - -#[test] -fn test_prune_negative_scored_peers() { - let config = Config::default(); - - //build mesh with one peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //add penalty to peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - //execute heartbeat - gs.heartbeat(); - - //peer should not be in mesh anymore - assert!(gs.mesh[&topics[0]].is_empty()); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - //no px in this case - peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_dont_graft_to_negative_scored_peers() { - let config = Config::default(); - //init full mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //add two additional peers that will not be part of the mesh - let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); - let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - - //reduce score of p1 to negative - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 1); - - //handle prunes of all other peers - for p in peers { - gs.handle_prune(&p, vec![(topics[0].clone(), Vec::new(), None)]); - } - - //heartbeat - gs.heartbeat(); - - //assert that mesh only contains p2 - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), 1); - assert!(gs.mesh.get(&topics[0]).unwrap().contains(&p2)); -} - -///Note that in this test also without a penalty the px would be ignored because of the -/// acceptPXThreshold, but the spec still explicitely states the rule that px from negative -/// peers should get ignored, therefore we test it here. -#[test] -fn test_ignore_px_from_negative_scored_peer() { - let config = Config::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //penalize peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - //handle prune from single peer with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - //assert no dials - assert_eq!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count(), - 0 - ); -} - -#[test] -fn test_only_send_nonnegative_scoring_peers_in_px() { - let config = ConfigBuilder::default() - .prune_peers(16) - .do_px() - .build() - .unwrap(); - - // Build mesh with three peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(3) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - // Penalize first peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - // Prune second peer - gs.send_graft_prune( - HashMap::new(), - vec![(peers[1], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - // Check that px in prune message only contains third peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers: px, - .. - }) => { - topic_hash == &topics[0] - && px.len() == 1 - && px[0].peer_id.as_ref().unwrap() == &peers[2] - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_gossip_to_peers_below_gossip_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - // Build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // Add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - // Reduce score of p1 below peer_score_thresholds.gossip_threshold - // note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - // Reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - // Receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - // Emit gossip - gs.emit_gossip(); - - // Check that exactly one gossip messages got sent and it got sent to p2 - let (control_msgs, _) = count_control_msgs(receivers, |peer, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => { - if topic_hash == &topics[0] && message_ids.iter().any(|id| id == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - // Build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // Add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - // Reduce score of p1 below peer_score_thresholds.gossip_threshold - // note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - // Reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - // Receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - gs.handle_iwant(&p1, vec![msg_id.clone()]); - gs.handle_iwant(&p2, vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = - receivers - .into_iter() - .fold(vec![], |mut collected_messages, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { - collected_messages.push((peer_id, message)); - } - } - collected_messages - }); - - //the message got sent to p2 - assert!(sent_messages - .iter() - .map(|(peer_id, msg)| ( - peer_id, - gs.data_transform.inbound_transform(msg.clone()).unwrap() - )) - .any(|(peer_id, msg)| peer_id == &p2 && gs.config.message_id(&msg) == msg_id)); - //the message got not sent to p1 - assert!(sent_messages - .iter() - .map(|(peer_id, msg)| ( - peer_id, - gs.data_transform.inbound_transform(msg.clone()).unwrap() - )) - .all(|(peer_id, msg)| !(peer_id == &p1 && gs.config.message_id(&msg) == msg_id))); -} - -#[test] -fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - //build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.gossip_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //message that other peers have - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - gs.handle_ihave(&p1, vec![(topics[0].clone(), vec![msg_id.clone()])]); - gs.handle_ihave(&p2, vec![(topics[0].clone(), vec![msg_id.clone()])]); - - // check that we sent exactly one IWANT request to p2 - let (control_msgs, _) = count_control_msgs(receivers, |peer, c| match c { - RpcOut::IWant(IWant { message_ids }) => { - if message_ids.iter().any(|m| m == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_publish_to_peer_below_publish_threshold() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - //build mesh with no peers and no subscribed topics - let (mut gs, _, mut receivers, _) = inject_nodes1() - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //create a new topic for which we are not subscribed - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two additional peers that will be added to the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //a heartbeat will remove the peers from the mesh - gs.heartbeat(); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(topic, publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_iter() - .fold(vec![], |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push((peer_id, message)); - } - } - collected_publish - }); - - //assert only published to p2 - assert_eq!(publishes.len(), 1); - assert_eq!(publishes[0].0, p2); -} - -#[test] -fn test_do_not_flood_publish_to_peer_below_publish_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - //build mesh with no peers - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .topics(vec!["test".into()]) - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //add two additional peers that will be added to the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //a heartbeat will remove the peers from the mesh - gs.heartbeat(); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_iter() - .fold(vec![], |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push((peer_id, message)) - } - } - collected_publish - }); - - //assert only published to p2 - assert_eq!(publishes.len(), 1); - assert!(publishes[0].0 == p2); -} - -#[test] -fn test_ignore_rpc_from_peers_below_graylist_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - graylist_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - //build mesh with no peers - let (mut gs, _, _, topics) = inject_nodes1() - .topics(vec!["test".into()]) - .gs_config(config.clone()) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //add two additional peers that will be added to the mesh - let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); - let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - - //reduce score of p1 below peer_score_thresholds.graylist_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below publish_threshold but not below graylist_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - let raw_message1 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4], - sequence_number: Some(1u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message2 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5], - sequence_number: Some(2u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message3 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5, 6], - sequence_number: Some(3u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message4 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5, 6, 7], - sequence_number: Some(4u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(raw_message2).unwrap(); - - // Transform the inbound message - let message4 = &gs.data_transform.inbound_transform(raw_message4).unwrap(); - - let subscription = Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topics[0].clone(), - }; - - let control_action = ControlAction::IHave(IHave { - topic_hash: topics[0].clone(), - message_ids: vec![config.message_id(message2)], - }); - - //clear events - gs.events.clear(); - - //receive from p1 - gs.on_connection_handler_event( - p1, - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![raw_message1], - subscriptions: vec![subscription.clone()], - control_msgs: vec![control_action], - }, - invalid_messages: Vec::new(), - }, - ); - - //only the subscription event gets processed, the rest is dropped - assert_eq!(gs.events.len(), 1); - assert!(matches!( - gs.events[0], - ToSwarm::GenerateEvent(Event::Subscribed { .. }) - )); - - let control_action = ControlAction::IHave(IHave { - topic_hash: topics[0].clone(), - message_ids: vec![config.message_id(message4)], - }); - - //receive from p2 - gs.on_connection_handler_event( - p2, - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![raw_message3], - subscriptions: vec![subscription], - control_msgs: vec![control_action], - }, - invalid_messages: Vec::new(), - }, - ); - - //events got processed - assert!(gs.events.len() > 1); -} - -#[test] -fn test_ignore_px_from_peers_below_accept_px_threshold() { - let config = ConfigBuilder::default().prune_peers(16).build().unwrap(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - accept_px_threshold: peer_score_params.app_specific_weight, - ..PeerScoreThresholds::default() - }; - // Build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Decrease score of first peer to less than accept_px_threshold - gs.set_application_score(&peers[0], 0.99); - - // Increase score of second peer to accept_px_threshold - gs.set_application_score(&peers[1], 1.0); - - // Handle prune from peer peers[0] with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - // Assert no dials - assert_eq!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count(), - 0 - ); - - //handle prune from peer peers[1] with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - gs.handle_prune( - &peers[1], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - //assert there are dials now - assert!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count() - > 0 - ); -} - -#[test] -fn test_keep_best_scoring_peers_on_oversubscription() { - let config = ConfigBuilder::default() - .mesh_n_low(15) - .mesh_n(30) - .mesh_n_high(60) - .retain_scores(29) - .build() - .unwrap(); - - //build mesh with more peers than mesh can hold - let n = config.mesh_n_high() + 1; - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(n) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - // graft all, will be accepted since the are outbound - for peer in &peers { - gs.handle_graft(peer, topics.clone()); - } - - //assign scores to peers equalling their index - - //set random positive scores - for (index, peer) in peers.iter().enumerate() { - gs.set_application_score(peer, index as f64); - } - - assert_eq!(gs.mesh[&topics[0]].len(), n); - - //heartbeat to prune some peers - gs.heartbeat(); - - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n()); - - //mesh contains retain_scores best peers - assert!(gs.mesh[&topics[0]].is_superset( - &peers[(n - config.retain_scores())..] - .iter() - .cloned() - .collect() - )); -} - -#[test] -fn test_scoring_p1() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 2.0, - time_in_mesh_quantum: Duration::from_millis(50), - time_in_mesh_cap: 10.0, - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params - .topics - .insert(topic_hash, topic_params.clone()); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //sleep for 2 times the mesh_quantum - sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - >= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be at least 2 * time_in_mesh_weight * topic_weight" - ); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - < 3.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be less than 3 * time_in_mesh_weight * topic_weight" - ); - - //sleep again for 2 times the mesh_quantum - sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - >= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be at least 4 * time_in_mesh_weight * topic_weight" - ); - - //sleep for enough periods to reach maximum - sleep(topic_params.time_in_mesh_quantum * (topic_params.time_in_mesh_cap - 3.0) as u32); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - topic_params.time_in_mesh_cap - * topic_params.time_in_mesh_weight - * topic_params.topic_weight, - "score should be exactly time_in_mesh_cap * time_in_mesh_weight * topic_weight" - ); -} - -fn random_message(seq: &mut u64, topics: &[TopicHash]) -> RawMessage { - let mut rng = rand::thread_rng(); - *seq += 1; - RawMessage { - source: Some(PeerId::random()), - data: (0..rng.gen_range(10..30)).map(|_| rng.gen()).collect(), - sequence_number: Some(*seq), - topic: topics[rng.gen_range(0..topics.len())].clone(), - signature: None, - key: None, - validated: true, - } -} - -#[test] -fn test_scoring_p2() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 2.0, - first_message_deliveries_cap: 10.0, - first_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params - .topics - .insert(topic_hash, topic_params.clone()); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let m1 = random_message(&mut seq, &topics); - //peer 0 delivers message first - deliver_message(&mut gs, 0, m1.clone()); - //peer 1 delivers message second - deliver_message(&mut gs, 1, m1); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight, - "score should be exactly first_message_deliveries_weight * topic_weight" - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 0.0, - "there should be no score for second message deliveries * topic_weight" - ); - - //peer 2 delivers two new messages - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 2.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight, - "score should be exactly 2 * first_message_deliveries_weight * topic_weight" - ); - - //test decaying - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.0 * topic_params.first_message_deliveries_decay - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly first_message_deliveries_decay * \ - first_message_deliveries_weight * topic_weight" - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 2.0 * topic_params.first_message_deliveries_decay - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly 2 * first_message_deliveries_decay * \ - first_message_deliveries_weight * topic_weight" - ); - - //test cap - for _ in 0..topic_params.first_message_deliveries_cap as u64 { - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - } - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - topic_params.first_message_deliveries_cap - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly first_message_deliveries_cap * \ - first_message_deliveries_weight * topic_weight" - ); -} - -#[test] -fn test_scoring_p3() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: -2.0, - mesh_message_deliveries_decay: 0.9, - mesh_message_deliveries_cap: 10.0, - mesh_message_deliveries_threshold: 5.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(100), - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let mut expected_message_deliveries = 0.0; - - //messages used to test window - let m1 = random_message(&mut seq, &topics); - let m2 = random_message(&mut seq, &topics); - - //peer 1 delivers m1 - deliver_message(&mut gs, 1, m1.clone()); - - //peer 0 delivers two message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 2.0; - - sleep(Duration::from_millis(60)); - - //peer 1 delivers m2 - deliver_message(&mut gs, 1, m2.clone()); - - sleep(Duration::from_millis(70)); - //peer 0 delivers m1 and m2 only m2 gets counted - deliver_message(&mut gs, 0, m1); - deliver_message(&mut gs, 0, m2); - expected_message_deliveries += 1.0; - - sleep(Duration::from_millis(900)); - - //message deliveries penalties get activated, peer 0 has only delivered 3 messages and - // therefore gets a penalty - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - (5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7 - ); - - // peer 0 delivers a lot of messages => message_deliveries should be capped at 10 - for _ in 0..20 { - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - } - - expected_message_deliveries = 10.0; - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //apply 10 decays - for _ in 0..10 { - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - } - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - (5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p3b() { - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(100)) - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: -2.0, - mesh_message_deliveries_decay: 0.9, - mesh_message_deliveries_cap: 10.0, - mesh_message_deliveries_threshold: 5.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(100), - mesh_failure_penalty_weight: -3.0, - mesh_failure_penalty_decay: 0.95, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let mut expected_message_deliveries = 0.0; - - //add some positive score - gs.peer_score - .as_mut() - .unwrap() - .0 - .set_application_score(&peers[0], 100.0); - - //peer 0 delivers two message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 2.0; - - sleep(Duration::from_millis(1050)); - - //activation kicks in - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - - //prune peer - gs.handle_prune(&peers[0], vec![(topics[0].clone(), vec![], None)]); - - //wait backoff - sleep(Duration::from_millis(130)); - - //regraft peer - gs.handle_graft(&peers[0], topics.clone()); - - //the score should now consider p3b - let mut expected_b3 = (5f64 - expected_message_deliveries).powi(2); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 100.0 + expected_b3 * -3.0 * 0.7 - ); - - //we can also add a new p3 to the score - - //peer 0 delivers one message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 1.0; - - sleep(Duration::from_millis(1050)); - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - expected_b3 *= 0.95; - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 100.0 + (expected_b3 * -3.0 + (5f64 - expected_message_deliveries).powi(2) * -2.0) * 0.7 - ); -} - -#[test] -fn test_scoring_p4_valid_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers valid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //message m1 gets validated - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Accept, - ) - .unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); -} - -#[test] -fn test_scoring_p4_invalid_signature() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - - //peer 0 delivers message with invalid signature - let m = random_message(&mut seq, &topics); - - gs.on_connection_handler_event( - peers[0], - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![], - subscriptions: vec![], - control_msgs: vec![], - }, - invalid_messages: vec![(m, ValidationError::InvalidSignature)], - }, - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_message_from_self() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message from self - let mut m = random_message(&mut seq, &topics); - m.source = Some(*gs.publish_config.get_own_id().unwrap()); - - deliver_message(&mut gs, 0, m); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_ignored_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers ignored message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - //message m1 gets ignored - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Ignore, - ) - .unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); -} - -#[test] -fn test_scoring_p4_application_invalidated_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_application_invalid_message_from_two_peers() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap(); - - //peer 1 delivers same message - deliver_message(&mut gs, 1, m1); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[1]), 0.0); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_three_application_invalid_messages() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers two invalid message - let m1 = random_message(&mut seq, &topics); - let m2 = random_message(&mut seq, &topics); - let m3 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - deliver_message(&mut gs, 0, m2.clone()); - deliver_message(&mut gs, 0, m3.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(m2).unwrap(); - // Transform the inbound message - let message3 = &gs.data_transform.inbound_transform(m3).unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //messages gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - gs.report_message_validation_result( - &config.message_id(message2), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - gs.report_message_validation_result( - &config.message_id(message3), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - //number of invalid messages gets squared - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 9.0 * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_decay() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); - - //we decay - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - // the number of invalids gets decayed to 0.9 and then squared in the score - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 0.9 * 0.9 * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p5() { - let peer_score_params = PeerScoreParams { - app_specific_weight: 2.0, - ..PeerScoreParams::default() - }; - - //build mesh with one peer - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - gs.set_application_score(&peers[0], 1.1); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.1 * 2.0 - ); -} - -#[test] -fn test_scoring_p6() { - let peer_score_params = PeerScoreParams { - ip_colocation_factor_threshold: 5.0, - ip_colocation_factor_weight: -2.0, - ..Default::default() - }; - - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(vec![]) - .to_subscribe(false) - .gs_config(Config::default()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - //create 5 peers with the same ip - let addr = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 3)); - let peers = vec![ - add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, true, addr.clone()).0, - ]; - - //create 4 other peers with other ip - let addr2 = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 4)); - let others = vec![ - add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, - ]; - - //no penalties yet - for peer in peers.iter().chain(others.iter()) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); - } - - //add additional connection for 3 others with addr - for id in others.iter().take(3) { - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: *id, - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr.clone(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 0, - })); - } - - //penalties apply squared - for peer in peers.iter().chain(others.iter().take(3)) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - //fourth other peer still no penalty - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&others[3]), 0.0); - - //add additional connection for 3 of the peers to addr2 - for peer in peers.iter().take(3) { - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: *peer, - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr2.clone(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 1, - })); - } - - //double penalties for the first three of each - for peer in peers.iter().take(3).chain(others.iter().take(3)) { - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(peer), - (9.0 + 4.0) * -2.0 - ); - } - - //single penalties for the rest - for peer in peers.iter().skip(3) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&others[3]), - 4.0 * -2.0 - ); - - //two times same ip doesn't count twice - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: peers[0], - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 2, - })); - - //nothing changed - //double penalties for the first three of each - for peer in peers.iter().take(3).chain(others.iter().take(3)) { - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(peer), - (9.0 + 4.0) * -2.0 - ); - } - - //single penalties for the rest - for peer in peers.iter().skip(3) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&others[3]), - 4.0 * -2.0 - ); -} - -#[test] -fn test_scoring_p7_grafts_before_backoff() { - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(200)) - .graft_flood_threshold(Duration::from_millis(100)) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - behaviour_penalty_weight: -2.0, - behaviour_penalty_decay: 0.9, - ..Default::default() - }; - - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - //remove peers from mesh and send prune to them => this adds a backoff for the peers - for peer in peers.iter().take(2) { - gs.mesh.get_mut(&topics[0]).unwrap().remove(peer); - gs.send_graft_prune( - HashMap::new(), - HashMap::from([(*peer, vec![topics[0].clone()])]), - HashSet::new(), - ); - } - - //wait 50 millisecs - sleep(Duration::from_millis(50)); - - //first peer tries to graft - gs.handle_graft(&peers[0], vec![topics[0].clone()]); - - //double behaviour penalty for first peer (squared) - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 4.0 * -2.0 - ); - - //wait 100 millisecs - sleep(Duration::from_millis(100)); - - //second peer tries to graft - gs.handle_graft(&peers[1], vec![topics[0].clone()]); - - //single behaviour penalty for second peer - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 1.0 * -2.0 - ); - - //test decay - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 4.0 * 0.9 * 0.9 * -2.0 - ); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 1.0 * 0.9 * 0.9 * -2.0 - ); -} - -#[test] -fn test_opportunistic_grafting() { - let config = ConfigBuilder::default() - .mesh_n_low(3) - .mesh_n(5) - .mesh_n_high(7) - .mesh_outbound_min(0) //deactivate outbound handling - .opportunistic_graft_ticks(2) - .opportunistic_graft_peers(2) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - app_specific_weight: 1.0, - ..Default::default() - }; - let thresholds = PeerScoreThresholds { - opportunistic_graft_threshold: 2.0, - ..Default::default() - }; - - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(5) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, thresholds))) - .create_network(); - - //fill mesh with 5 peers - for peer in &peers { - gs.handle_graft(peer, topics.clone()); - } - - //add additional 5 peers - let others: Vec<_> = (0..5) - .map(|_| add_peer(&mut gs, &topics, false, false)) - .collect(); - - //currently mesh equals peers - assert_eq!(gs.mesh[&topics[0]], peers.iter().cloned().collect()); - - //give others high scores (but the first two have not high enough scores) - for (i, peer) in peers.iter().enumerate().take(5) { - gs.set_application_score(peer, 0.0 + i as f64); - } - - //set scores for peers in the mesh - for (i, (peer, _receiver)) in others.iter().enumerate().take(5) { - gs.set_application_score(peer, 0.0 + i as f64); - } - - //this gives a median of exactly 2.0 => should not apply opportunistic grafting - gs.heartbeat(); - gs.heartbeat(); - - assert_eq!( - gs.mesh[&topics[0]].len(), - 5, - "should not apply opportunistic grafting" - ); - - //reduce middle score to 1.0 giving a median of 1.0 - gs.set_application_score(&peers[2], 1.0); - - //opportunistic grafting after two heartbeats - - gs.heartbeat(); - assert_eq!( - gs.mesh[&topics[0]].len(), - 5, - "should not apply opportunistic grafting after first tick" - ); - - gs.heartbeat(); - - assert_eq!( - gs.mesh[&topics[0]].len(), - 7, - "opportunistic grafting should have added 2 peers" - ); - - assert!( - gs.mesh[&topics[0]].is_superset(&peers.iter().cloned().collect()), - "old peers are still part of the mesh" - ); - - assert!( - gs.mesh[&topics[0]].is_disjoint(&others.iter().map(|(p, _)| p).cloned().take(2).collect()), - "peers below or equal to median should not be added in opportunistic grafting" - ); -} - -#[test] -fn test_ignore_graft_from_unknown_topic() { - //build gossipsub without subscribing to any topics - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(1) - .topics(vec![]) - .to_subscribe(false) - .create_network(); - - //handle an incoming graft for some topic - gs.handle_graft(&peers[0], vec![Topic::new("test").hash()]); - - //assert that no prune got created - let (control_msgs, _) = count_control_msgs(receivers, |_, a| matches!(a, RpcOut::Prune { .. })); - assert_eq!( - control_msgs, 0, - "we should not prune after graft in unknown topic" - ); -} - -#[test] -fn test_ignore_too_many_iwants_from_same_peer_for_same_message() { - let config = Config::default(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //receive a message - let mut seq = 0; - let m1 = random_message(&mut seq, &topics); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap(); - - let id = config.message_id(message1); - - gs.handle_received_message(m1, &PeerId::random()); - - //clear events - let receivers = flush_events(&mut gs, receivers); - - //the first gossip_retransimission many iwants return the valid message, all others are - // ignored. - for _ in 0..(2 * config.gossip_retransimission() + 10) { - gs.handle_iwant(&peer, vec![id.clone()]); - } - - assert_eq!( - receivers.into_values().fold(0, |mut fwds, c| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { - fwds += 1; - } - } - fwds - }), - config.gossip_retransimission() as usize, - "not more then gossip_retransmission many messages get sent back" - ); -} - -#[test] -fn test_ignore_too_many_ihaves() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //peer has 20 messages - let mut seq = 0; - let messages: Vec<_> = (0..20).map(|_| random_message(&mut seq, &topics)).collect(); - - //peer sends us one ihave for each message in order - for raw_message in &messages { - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), vec![config.message_id(message)])], - ); - } - - let first_ten: HashSet<_> = messages - .iter() - .take(10) - .map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap()) - .map(|m| config.message_id(&m)) - .collect(); - - //we send iwant only for the first 10 messages - let (control_msgs, receivers) = count_control_msgs(receivers, |p, action| { - p == &peer - && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1 && first_ten.contains(&message_ids[0])) - }); - assert_eq!( - control_msgs, 10, - "exactly the first ten ihaves should be processed and one iwant for each created" - ); - - //after a heartbeat everything is forgotten - gs.heartbeat(); - - for raw_message in messages[10..].iter() { - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), vec![config.message_id(message)])], - ); - } - - //we sent iwant for all 10 messages - let (control_msgs, _) = count_control_msgs(receivers, |p, action| { - p == &peer - && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1) - }); - assert_eq!(control_msgs, 10, "all 20 should get sent"); -} - -#[test] -fn test_ignore_too_many_messages_in_ihave() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .max_ihave_length(10) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //peer has 30 messages - let mut seq = 0; - let message_ids: Vec<_> = (0..30) - .map(|_| random_message(&mut seq, &topics)) - .map(|msg| gs.data_transform.inbound_transform(msg).unwrap()) - .map(|msg| config.message_id(&msg)) - .collect(); - - //peer sends us three ihaves - gs.handle_ihave(&peer, vec![(topics[0].clone(), message_ids[0..8].to_vec())]); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[0..12].to_vec())], - ); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[0..20].to_vec())], - ); - - let first_twelve: HashSet<_> = message_ids.iter().take(12).collect(); - - //we send iwant only for the first 10 messages - let mut sum = 0; - let (control_msgs, receivers) = count_control_msgs(receivers, |p, rpc| match rpc { - RpcOut::IWant(IWant { message_ids }) => { - p == &peer && { - assert!(first_twelve.is_superset(&message_ids.iter().collect())); - sum += message_ids.len(); - true - } - } - _ => false, - }); - assert_eq!( - control_msgs, 2, - "the third ihave should get ignored and no iwant sent" - ); - - assert_eq!(sum, 10, "exactly the first ten ihaves should be processed"); - - //after a heartbeat everything is forgotten - gs.heartbeat(); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[20..30].to_vec())], - ); - - //we sent 10 iwant messages ids via a IWANT rpc. - let mut sum = 0; - let (control_msgs, _) = count_control_msgs(receivers, |p, rpc| match rpc { - RpcOut::IWant(IWant { message_ids }) => { - p == &peer && { - sum += message_ids.len(); - true - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); - assert_eq!(sum, 10, "exactly 20 iwants should get sent"); -} - -#[test] -fn test_limit_number_of_message_ids_inside_ihave() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .max_ihave_length(100) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - //graft to all peers to really fill the mesh with all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //add two other peers not in the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //receive 200 messages from another peer - let mut seq = 0; - for _ in 0..200 { - gs.handle_received_message(random_message(&mut seq, &topics), &PeerId::random()); - } - - //emit gossip - gs.emit_gossip(); - - // both peers should have gotten 100 random ihave messages, to asser the randomness, we - // assert that both have not gotten the same set of messages, but have an intersection - // (which is the case with very high probability, the probabiltity of failure is < 10^-58). - - let mut ihaves1 = HashSet::new(); - let mut ihaves2 = HashSet::new(); - - let (control_msgs, _) = count_control_msgs(receivers, |p, action| match action { - RpcOut::IHave(IHave { message_ids, .. }) => { - if p == &p1 { - ihaves1 = message_ids.iter().cloned().collect(); - true - } else if p == &p2 { - ihaves2 = message_ids.iter().cloned().collect(); - true - } else { - false - } - } - _ => false, - }); - assert_eq!( - control_msgs, 2, - "should have emitted one ihave to p1 and one to p2" - ); - - assert_eq!( - ihaves1.len(), - 100, - "should have sent 100 message ids in ihave to p1" - ); - assert_eq!( - ihaves2.len(), - 100, - "should have sent 100 message ids in ihave to p2" - ); - assert!( - ihaves1 != ihaves2, - "should have sent different random messages to p1 and p2 \ - (this may fail with a probability < 10^-58" - ); - assert!( - ihaves1.intersection(&ihaves2).count() > 0, - "should have sent random messages with some common messages to p1 and p2 \ - (this may fail with a probability < 10^-58" - ); -} - -#[test] -fn test_iwant_penalties() { - /* - use tracing_subscriber::EnvFilter; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - */ - let config = ConfigBuilder::default() - .iwant_followup_time(Duration::from_secs(4)) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - behaviour_penalty_weight: -1.0, - ..Default::default() - }; - - // fill the mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - // graft to all peers to really fill the mesh with all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // add 100 more peers - let other_peers: Vec<_> = (0..100) - .map(|_| add_peer(&mut gs, &topics, false, false)) - .collect(); - - // each peer sends us an ihave containing each two message ids - let mut first_messages = Vec::new(); - let mut second_messages = Vec::new(); - let mut seq = 0; - for (peer, _receiver) in &other_peers { - let msg1 = random_message(&mut seq, &topics); - let msg2 = random_message(&mut seq, &topics); - - // Decompress the raw message and calculate the message id. - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(msg1.clone()).unwrap(); - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(msg2.clone()).unwrap(); - - first_messages.push(msg1.clone()); - second_messages.push(msg2.clone()); - gs.handle_ihave( - peer, - vec![( - topics[0].clone(), - vec![config.message_id(message1), config.message_id(message2)], - )], - ); - } - - // the peers send us all the first message ids in time - for (index, (peer, _receiver)) in other_peers.iter().enumerate() { - gs.handle_received_message(first_messages[index].clone(), peer); - } - - // now we do a heartbeat no penalization should have been applied yet - gs.heartbeat(); - - for (peer, _receiver) in &other_peers { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); - } - - // receive the first twenty of the other peers then send their response - for (index, (peer, _receiver)) in other_peers.iter().enumerate().take(20) { - gs.handle_received_message(second_messages[index].clone(), peer); - } - - // sleep for the promise duration - sleep(Duration::from_secs(4)); - - // now we do a heartbeat to apply penalization - gs.heartbeat(); - - // now we get the second messages from the last 80 peers. - for (index, (peer, _receiver)) in other_peers.iter().enumerate() { - if index > 19 { - gs.handle_received_message(second_messages[index].clone(), peer); - } - } - - // no further penalizations should get applied - gs.heartbeat(); - - // Only the last 80 peers should be penalized for not responding in time - let mut not_penalized = 0; - let mut single_penalized = 0; - let mut double_penalized = 0; - - for (i, (peer, _receiver)) in other_peers.iter().enumerate() { - let score = gs.peer_score.as_ref().unwrap().0.score(peer); - if score == 0.0 { - not_penalized += 1; - } else if score == -1.0 { - assert!(i > 9); - single_penalized += 1; - } else if score == -4.0 { - assert!(i > 9); - double_penalized += 1 - } else { - println!("{peer}"); - println!("{score}"); - panic!("Invalid score of peer"); - } - } - - assert_eq!(not_penalized, 20); - assert_eq!(single_penalized, 80); - assert_eq!(double_penalized, 0); -} - -#[test] -fn test_publish_to_floodsub_peers_without_flood_publish() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_low() - 1) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - //add two floodsub peer, one explicit, one implicit - let (p1, receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - receivers.insert(p1, receiver1); - - let (p2, receiver2) = - add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - receivers.insert(p2, receiver2); - - //p1 and p2 are not in the mesh - assert!(!gs.mesh[&topics[0]].contains(&p1) && !gs.mesh[&topics[0]].contains(&p2)); - - //publish a message - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect publish messages to floodsub peers - let publishes = receivers - .into_iter() - .fold(0, |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if matches!(priority.try_recv(), - Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) - { - collected_publish += 1; - } - } - collected_publish - }); - - assert_eq!( - publishes, 2, - "Should send a publish message to all floodsub peers" - ); -} - -#[test] -fn test_do_not_use_floodsub_in_fanout() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let (mut gs, _, mut receivers, _) = inject_nodes1() - .peer_no(config.mesh_n_low() - 1) - .topics(Vec::new()) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two floodsub peer, one explicit, one implicit - let (p1, receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - - receivers.insert(p1, receiver1); - let (p2, receiver2) = - add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - receivers.insert(p2, receiver2); - //publish a message - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect publish messages to floodsub peers - let publishes = receivers - .into_iter() - .fold(0, |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if matches!(priority.try_recv(), - Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) - { - collected_publish += 1; - } - } - collected_publish - }); - - assert_eq!( - publishes, 2, - "Should send a publish message to all floodsub peers" - ); - - assert!( - !gs.fanout[&topics[0]].contains(&p1) && !gs.fanout[&topics[0]].contains(&p2), - "Floodsub peers are not allowed in fanout" - ); -} - -#[test] -fn test_dont_add_floodsub_peers_to_mesh_on_join() { - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(Vec::new()) - .to_subscribe(false) - .create_network(); - - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two floodsub peer, one explicit, one implicit - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - gs.join(&topics[0]); - - assert!( - gs.mesh[&topics[0]].is_empty(), - "Floodsub peers should not get added to mesh" - ); -} - -#[test] -fn test_dont_send_px_to_old_gossipsub_peers() { - let (mut gs, _, receivers, topics) = inject_nodes1() - .peer_no(0) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add an old gossipsub peer - let (p1, _receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Gossipsub), - ); - - //prune the peer - gs.send_graft_prune( - HashMap::new(), - vec![(p1, topics.clone())].into_iter().collect(), - HashSet::new(), - ); - - //check that prune does not contain px - let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), - _ => false, - }); - assert_eq!(control_msgs, 0, "Should not send px to floodsub peers"); -} - -#[test] -fn test_dont_send_floodsub_peers_in_px() { - //build mesh with one peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //add two floodsub peers - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - //prune only mesh node - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], topics.clone())].into_iter().collect(), - HashSet::new(), - ); - - //check that px in prune message is empty - let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), - _ => false, - }); - assert_eq!(control_msgs, 0, "Should not include floodsub peers in px"); -} - -#[test] -fn test_dont_add_floodsub_peers_to_mesh_in_heartbeat() { - let (mut gs, _, _, topics) = inject_nodes1() - .peer_no(0) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add two floodsub peer, one explicit, one implicit - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - true, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, true, false, Multiaddr::empty(), None); - - gs.heartbeat(); - - assert!( - gs.mesh[&topics[0]].is_empty(), - "Floodsub peers should not get added to mesh" - ); -} - -// Some very basic test of public api methods. -#[test] -fn test_public_api() { - let (gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - let peers = peers.into_iter().collect::>(); - - assert_eq!( - gs.topics().cloned().collect::>(), - topic_hashes, - "Expected topics to match registered topic." - ); - - assert_eq!( - gs.mesh_peers(&TopicHash::from_raw("topic1")) - .cloned() - .collect::>(), - peers, - "Expected peers for a registered topic to contain all peers." - ); - - assert_eq!( - gs.all_mesh_peers().cloned().collect::>(), - peers, - "Expected all_peers to contain all peers." - ); -} - -#[test] -fn test_subscribe_to_invalid_topic() { - let t1 = Topic::new("t1"); - let t2 = Topic::new("t2"); - let (mut gs, _, _, _) = inject_nodes::() - .subscription_filter(WhitelistSubscriptionFilter( - vec![t1.hash()].into_iter().collect(), - )) - .to_subscribe(false) - .create_network(); - - assert!(gs.subscribe(&t1).is_ok()); - assert!(gs.subscribe(&t2).is_err()); -} - -#[test] -fn test_subscribe_and_graft_with_negative_score() { - //simulate a communication between two gossipsub instances - let (mut gs1, _, _, topic_hashes) = inject_nodes1() - .topics(vec!["test".into()]) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - let (mut gs2, _, receivers, _) = inject_nodes1().create_network(); - - let connection_id = ConnectionId::new_unchecked(0); - - let topic = Topic::new("test"); - - let (p2, _receiver1) = add_peer(&mut gs1, &Vec::new(), true, false); - let (p1, _receiver2) = add_peer(&mut gs2, &topic_hashes, false, false); - - //add penalty to peer p2 - gs1.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - let original_score = gs1.peer_score.as_ref().unwrap().0.score(&p2); - - //subscribe to topic in gs2 - gs2.subscribe(&topic).unwrap(); - - let forward_messages_to_p1 = |gs1: &mut Behaviour<_, _>, - p1: PeerId, - p2: PeerId, - connection_id: ConnectionId, - receivers: HashMap| - -> HashMap { - let new_receivers = HashMap::new(); - for (peer_id, receiver) in receivers.into_iter() { - let non_priority = receiver.non_priority.into_inner(); - match non_priority.try_recv() { - Ok(rpc) if peer_id == p1 => { - gs1.on_connection_handler_event( - p2, - connection_id, - HandlerEvent::Message { - rpc: proto_to_message(&rpc.into_protobuf()), - invalid_messages: vec![], - }, - ); - } - _ => {} - } - } - new_receivers - }; - - //forward the subscribe message - let receivers = forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - - //heartbeats on both - gs1.heartbeat(); - gs2.heartbeat(); - - //forward messages again - forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - - //nobody got penalized - assert!(gs1.peer_score.as_ref().unwrap().0.score(&p2) >= original_score); -} - -#[test] -/// Test nodes that send grafts without subscriptions. -fn test_graft_without_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let topic = String::from("test_subscribe"); - let subscribe_topic = vec![topic.clone()]; - let subscribe_topic_hash = vec![Topic::new(topic.clone()).hash()]; - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(subscribe_topic) - .to_subscribe(false) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // The node sends a graft for the subscribe topic. - gs.handle_graft(&peers[0], subscribe_topic_hash); - - // The node disconnects - disconnect_peer(&mut gs, &peers[0]); - - // We unsubscribe from the topic. - let _ = gs.unsubscribe(&Topic::new(topic)); -} - -/// Test that a node sends IDONTWANT messages to the mesh peers -/// that run Gossipsub v1.2. -#[test] -fn sends_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12u8; 1024], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { - assert_ne!(peer_id, peers[1]); - idontwants += 1; - } - } - idontwants - }), - 3, - "IDONTWANT was not sent" - ); -} - -#[test] -fn doesnt_sends_idontwant_for_lower_message_size() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { - assert_ne!(peer_id, peers[1]); - idontwants += 1; - } - } - idontwants - }), - 0, - "IDONTWANT was sent" - ); -} - -/// Test that a node doesn't send IDONTWANT messages to the mesh peers -/// that don't run Gossipsub v1.2. -#[test] -fn doesnt_send_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::IDontWant(_)) if peer_id != peers[1]) { - idontwants += 1; - } - } - idontwants - }), - 0, - "IDONTWANT were sent" - ); -} - -/// Test that a node doesn't forward a messages to the mesh peers -/// that sent IDONTWANT. -#[test] -fn doesnt_forward_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let raw_message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - let message = gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - let message_id = gs.config.message_id(&message); - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - peer.dont_send_received.insert(message_id, Instant::now()); - - gs.handle_received_message(raw_message.clone(), &local_id); - assert_eq!( - receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { - assert_ne!(peer_id, peers[2]); - fwds += 1; - } - } - fwds - }), - 2, - "IDONTWANT was not sent" - ); -} - -/// Test that a node parses an -/// IDONTWANT message to the respective peer. -#[test] -fn parses_idontwant() { - let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let message_id = MessageId::new(&[0, 1, 2, 3]); - let rpc = Rpc { - messages: vec![], - subscriptions: vec![], - control_msgs: vec![ControlAction::IDontWant(IDontWant { - message_ids: vec![message_id.clone()], - })], - }; - gs.on_connection_handler_event( - peers[1], - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc, - invalid_messages: vec![], - }, - ); - let peer = gs.connected_peers.get_mut(&peers[1]).unwrap(); - assert!(peer.dont_send_received.get(&message_id).is_some()); -} - -/// Test that a node clears stale IDONTWANT messages. -#[test] -fn clear_stale_idontwant() { - let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - peer.dont_send_received - .insert(MessageId::new(&[1, 2, 3, 4]), Instant::now()); - std::thread::sleep(Duration::from_secs(3)); - gs.heartbeat(); - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - assert!(peer.dont_send_received.is_empty()); -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/config.rs b/beacon_node/lighthouse_network/gossipsub/src/config.rs deleted file mode 100644 index eb8dd432a3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/config.rs +++ /dev/null @@ -1,1051 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::borrow::Cow; -use std::sync::Arc; -use std::time::Duration; - -use super::error::ConfigBuilderError; -use super::protocol::{ProtocolConfig, ProtocolId, FLOODSUB_PROTOCOL}; -use super::types::{Message, MessageId, PeerKind}; - -use libp2p::identity::PeerId; -use libp2p::swarm::StreamProtocol; - -/// The types of message validation that can be employed by gossipsub. -#[derive(Debug, Clone)] -pub enum ValidationMode { - /// This is the default setting. This requires the message author to be a valid [`PeerId`] and to - /// be present as well as the sequence number. All messages must have valid signatures. - /// - /// NOTE: This setting will reject messages from nodes using - /// [`crate::behaviour::MessageAuthenticity::Anonymous`] and all messages that do not have - /// signatures. - Strict, - /// This setting permits messages that have no author, sequence number or signature. If any of - /// these fields exist in the message these are validated. - Permissive, - /// This setting requires the author, sequence number and signature fields of a message to be - /// empty. Any message that contains these fields is considered invalid. - Anonymous, - /// This setting does not check the author, sequence number or signature fields of incoming - /// messages. If these fields contain data, they are simply ignored. - /// - /// NOTE: This setting will consider messages with invalid signatures as valid messages. - None, -} - -/// Selector for custom Protocol Id -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Version { - V1_0, - V1_1, -} - -/// Configuration parameters that define the performance of the gossipsub network. -#[derive(Clone)] -pub struct Config { - protocol: ProtocolConfig, - history_length: usize, - history_gossip: usize, - mesh_n: usize, - mesh_n_low: usize, - mesh_n_high: usize, - retain_scores: usize, - gossip_lazy: usize, - gossip_factor: f64, - heartbeat_initial_delay: Duration, - heartbeat_interval: Duration, - fanout_ttl: Duration, - check_explicit_peers_ticks: u64, - duplicate_cache_time: Duration, - validate_messages: bool, - message_id_fn: Arc MessageId + Send + Sync + 'static>, - allow_self_origin: bool, - do_px: bool, - prune_peers: usize, - prune_backoff: Duration, - unsubscribe_backoff: Duration, - backoff_slack: u32, - flood_publish: bool, - graft_flood_threshold: Duration, - mesh_outbound_min: usize, - opportunistic_graft_ticks: u64, - opportunistic_graft_peers: usize, - gossip_retransimission: u32, - max_messages_per_rpc: Option, - max_ihave_length: usize, - max_ihave_messages: usize, - iwant_followup_time: Duration, - published_message_ids_cache_time: Duration, - connection_handler_queue_len: usize, - connection_handler_publish_duration: Duration, - connection_handler_forward_duration: Duration, - idontwant_message_size_threshold: usize, -} - -impl Config { - pub(crate) fn protocol_config(&self) -> ProtocolConfig { - self.protocol.clone() - } - - // Overlay network parameters. - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub fn history_length(&self) -> usize { - self.history_length - } - - /// Number of past heartbeats to gossip about (default is 3). - pub fn history_gossip(&self) -> usize { - self.history_gossip - } - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub fn mesh_n(&self) -> usize { - self.mesh_n - } - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 5). - pub fn mesh_n_low(&self) -> usize { - self.mesh_n_low - } - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub fn mesh_n_high(&self) -> usize { - self.mesh_n_high - } - - /// Affects how peers are selected when pruning a mesh due to over subscription. - /// - /// At least `retain_scores` of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). - pub fn retain_scores(&self) -> usize { - self.retain_scores - } - - /// Minimum number of peers to emit gossip to during a heartbeat (D_lazy in the spec, - /// default is 6). - pub fn gossip_lazy(&self) -> usize { - self.gossip_lazy - } - - /// Affects how many peers we will emit gossip to at each heartbeat. - /// - /// We will send gossip to `gossip_factor * (total number of non-mesh peers)`, or - /// `gossip_lazy`, whichever is greater. The default is 0.25. - pub fn gossip_factor(&self) -> f64 { - self.gossip_factor - } - - /// Initial delay in each heartbeat (default is 5 seconds). - pub fn heartbeat_initial_delay(&self) -> Duration { - self.heartbeat_initial_delay - } - - /// Time between each heartbeat (default is 1 second). - pub fn heartbeat_interval(&self) -> Duration { - self.heartbeat_interval - } - - /// Time to live for fanout peers (default is 60 seconds). - pub fn fanout_ttl(&self) -> Duration { - self.fanout_ttl - } - - /// The number of heartbeat ticks until we recheck the connection to explicit peers and - /// reconnecting if necessary (default 300). - pub fn check_explicit_peers_ticks(&self) -> u64 { - self.check_explicit_peers_ticks - } - - /// The maximum byte size for each gossipsub RPC (default is 65536 bytes). - /// - /// This represents the maximum size of the entire protobuf payload. It must be at least - /// large enough to support basic control messages. If Peer eXchange is enabled, this - /// must be large enough to transmit the desired peer information on pruning. It must be at - /// least 100 bytes. Default is 65536 bytes. - pub fn max_transmit_size(&self) -> usize { - self.protocol.max_transmit_size - } - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub fn duplicate_cache_time(&self) -> Duration { - self.duplicate_cache_time - } - - /// When set to `true`, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set to - /// true, the user must manually call [`crate::Behaviour::report_message_validation_result()`] - /// on the behaviour to forward message once validated (default is `false`). - /// The default is `false`. - pub fn validate_messages(&self) -> bool { - self.validate_messages - } - - /// Determines the level of validation used when receiving messages. See [`ValidationMode`] - /// for the available types. The default is ValidationMode::Strict. - pub fn validation_mode(&self) -> &ValidationMode { - &self.protocol.validation_mode - } - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a [`Message`] as input and outputs a String to be interpreted as - /// the message id. - pub fn message_id(&self, message: &Message) -> MessageId { - (self.message_id_fn)(message) - } - - /// By default, gossipsub will reject messages that are sent to us that have the same message - /// source as we have specified locally. Enabling this, allows these messages and prevents - /// penalizing the peer that sent us the message. Default is false. - pub fn allow_self_origin(&self) -> bool { - self.allow_self_origin - } - - /// Whether Peer eXchange is enabled; this should be enabled in bootstrappers and other well - /// connected/trusted nodes. The default is false. - /// - /// Note: Peer exchange is not implemented today, see - /// . - pub fn do_px(&self) -> bool { - self.do_px - } - - /// Controls the number of peers to include in prune Peer eXchange. - /// When we prune a peer that's eligible for PX (has a good score, etc), we will try to - /// send them signed peer records for up to `prune_peers` other peers that we - /// know of. It is recommended that this value is larger than `mesh_n_high` so that the pruned - /// peer can reliably form a full mesh. The default is typically 16 however until signed - /// records are spec'd this is disabled and set to 0. - pub fn prune_peers(&self) -> usize { - self.prune_peers - } - - /// Controls the backoff time for pruned peers. This is how long - /// a peer must wait before attempting to graft into our mesh again after being pruned. - /// When pruning a peer, we send them our value of `prune_backoff` so they know - /// the minimum time to wait. Peers running older versions may not send a backoff time, - /// so if we receive a prune message without one, we will wait at least `prune_backoff` - /// before attempting to re-graft. The default is one minute. - pub fn prune_backoff(&self) -> Duration { - self.prune_backoff - } - - /// Controls the backoff time when unsubscribing from a topic. - /// - /// This is how long to wait before resubscribing to the topic. A short backoff period in case - /// of an unsubscribe event allows reaching a healthy mesh in a more timely manner. The default - /// is 10 seconds. - pub fn unsubscribe_backoff(&self) -> Duration { - self.unsubscribe_backoff - } - - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait - /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if - /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing - /// prunes on our side and processing prunes on the receiving side this guarantees that we - /// get not punished for too early grafting. The default is 1. - pub fn backoff_slack(&self) -> u32 { - self.backoff_slack - } - - /// Whether to do flood publishing or not. If enabled newly created messages will always be - /// sent to all peers that are subscribed to the topic and have a good enough score. - /// The default is true. - pub fn flood_publish(&self) -> bool { - self.flood_publish - } - - /// If a GRAFT comes before `graft_flood_threshold` has elapsed since the last PRUNE, - /// then there is an extra score penalty applied to the peer through P7. - pub fn graft_flood_threshold(&self) -> Duration { - self.graft_flood_threshold - } - - /// Minimum number of outbound peers in the mesh network before adding more (D_out in the spec). - /// This value must be smaller or equal than `mesh_n / 2` and smaller than `mesh_n_low`. - /// The default is 2. - pub fn mesh_outbound_min(&self) -> usize { - self.mesh_outbound_min - } - - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is - /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh - /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a - /// threshold (see ). - /// The default is 60. - pub fn opportunistic_graft_ticks(&self) -> u64 { - self.opportunistic_graft_ticks - } - - /// Controls how many times we will allow a peer to request the same message id through IWANT - /// gossip before we start ignoring them. This is designed to prevent peers from spamming us - /// with requests and wasting our resources. The default is 3. - pub fn gossip_retransimission(&self) -> u32 { - self.gossip_retransimission - } - - /// The maximum number of new peers to graft to during opportunistic grafting. The default is 2. - pub fn opportunistic_graft_peers(&self) -> usize { - self.opportunistic_graft_peers - } - - /// The maximum number of messages we will process in a given RPC. If this is unset, there is - /// no limit. The default is None. - pub fn max_messages_per_rpc(&self) -> Option { - self.max_messages_per_rpc - } - - /// The maximum number of messages to include in an IHAVE message. - /// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a - /// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the - /// default if your system is pushing more than 5000 messages in GossipSubHistoryGossip - /// heartbeats; with the defaults this is 1666 messages/s. The default is 5000. - pub fn max_ihave_length(&self) -> usize { - self.max_ihave_length - } - - /// GossipSubMaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer - /// within a heartbeat. - pub fn max_ihave_messages(&self) -> usize { - self.max_ihave_messages - } - - /// Time to wait for a message requested through IWANT following an IHAVE advertisement. - /// If the message is not received within this window, a broken promise is declared and - /// the router may apply behavioural penalties. The default is 3 seconds. - pub fn iwant_followup_time(&self) -> Duration { - self.iwant_followup_time - } - - /// Enable support for flooodsub peers. Default false. - pub fn support_floodsub(&self) -> bool { - self.protocol.protocol_ids.contains(&FLOODSUB_PROTOCOL) - } - - /// Published message ids time cache duration. The default is 10 seconds. - pub fn published_message_ids_cache_time(&self) -> Duration { - self.published_message_ids_cache_time - } - - /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. - pub fn connection_handler_queue_len(&self) -> usize { - self.connection_handler_queue_len - } - - /// The duration a message to be published can wait to be sent before it is abandoned. The - /// default is 5 seconds. - pub fn publish_queue_duration(&self) -> Duration { - self.connection_handler_publish_duration - } - - /// The duration a message to be forwarded can wait to be sent before it is abandoned. The - /// default is 1s. - pub fn forward_queue_duration(&self) -> Duration { - self.connection_handler_forward_duration - } - - // The message size threshold for which IDONTWANT messages are sent. - // Sending IDONTWANT messages for small messages can have a negative effect to the overall - // traffic and CPU load. This acts as a lower bound cutoff for the message size to which - // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 - // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) - // default is 1kB - pub fn idontwant_message_size_threshold(&self) -> usize { - self.idontwant_message_size_threshold - } -} - -impl Default for Config { - fn default() -> Self { - // use ConfigBuilder to also validate defaults - ConfigBuilder::default() - .build() - .expect("Default config parameters should be valid parameters") - } -} - -/// The builder struct for constructing a gossipsub configuration. -pub struct ConfigBuilder { - config: Config, - invalid_protocol: bool, // This is a bit of a hack to only expose one error to the user. -} - -impl Default for ConfigBuilder { - fn default() -> Self { - ConfigBuilder { - config: Config { - protocol: ProtocolConfig::default(), - history_length: 5, - history_gossip: 3, - mesh_n: 6, - mesh_n_low: 5, - mesh_n_high: 12, - retain_scores: 4, - gossip_lazy: 6, // default to mesh_n - gossip_factor: 0.25, - heartbeat_initial_delay: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(1), - fanout_ttl: Duration::from_secs(60), - check_explicit_peers_ticks: 300, - duplicate_cache_time: Duration::from_secs(60), - validate_messages: false, - message_id_fn: Arc::new(|message| { - // default message id is: source + sequence number - // NOTE: If either the peer_id or source is not provided, we set to 0; - let mut source_string = if let Some(peer_id) = message.source.as_ref() { - peer_id.to_base58() - } else { - PeerId::from_bytes(&[0, 1, 0]) - .expect("Valid peer id") - .to_base58() - }; - source_string - .push_str(&message.sequence_number.unwrap_or_default().to_string()); - MessageId::from(source_string) - }), - allow_self_origin: false, - do_px: false, - prune_peers: 0, // NOTE: Increasing this currently has little effect until Signed records are implemented. - prune_backoff: Duration::from_secs(60), - unsubscribe_backoff: Duration::from_secs(10), - backoff_slack: 1, - flood_publish: true, - graft_flood_threshold: Duration::from_secs(10), - mesh_outbound_min: 2, - opportunistic_graft_ticks: 60, - opportunistic_graft_peers: 2, - gossip_retransimission: 3, - max_messages_per_rpc: None, - max_ihave_length: 5000, - max_ihave_messages: 10, - iwant_followup_time: Duration::from_secs(3), - published_message_ids_cache_time: Duration::from_secs(10), - connection_handler_queue_len: 5000, - connection_handler_publish_duration: Duration::from_secs(5), - connection_handler_forward_duration: Duration::from_millis(1000), - idontwant_message_size_threshold: 1000, - }, - invalid_protocol: false, - } - } -} - -impl From for ConfigBuilder { - fn from(config: Config) -> Self { - ConfigBuilder { - config, - invalid_protocol: false, - } - } -} - -impl ConfigBuilder { - /// The protocol id prefix to negotiate this protocol (default is `/meshsub/1.1.0` and `/meshsub/1.0.0`). - pub fn protocol_id_prefix( - &mut self, - protocol_id_prefix: impl Into>, - ) -> &mut Self { - let cow = protocol_id_prefix.into(); - - match ( - StreamProtocol::try_from_owned(format!("{}/1.1.0", cow)), - StreamProtocol::try_from_owned(format!("{}/1.0.0", cow)), - ) { - (Ok(p1), Ok(p2)) => { - self.config.protocol.protocol_ids = vec![ - ProtocolId { - protocol: p1, - kind: PeerKind::Gossipsubv1_1, - }, - ProtocolId { - protocol: p2, - kind: PeerKind::Gossipsub, - }, - ] - } - _ => { - self.invalid_protocol = true; - } - } - - self - } - - /// The full protocol id to negotiate this protocol (does not append `/1.0.0` or `/1.1.0`). - pub fn protocol_id( - &mut self, - protocol_id: impl Into>, - custom_id_version: Version, - ) -> &mut Self { - let cow = protocol_id.into(); - - match StreamProtocol::try_from_owned(cow.to_string()) { - Ok(protocol) => { - self.config.protocol.protocol_ids = vec![ProtocolId { - protocol, - kind: match custom_id_version { - Version::V1_1 => PeerKind::Gossipsubv1_1, - Version::V1_0 => PeerKind::Gossipsub, - }, - }] - } - _ => { - self.invalid_protocol = true; - } - } - - self - } - - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub fn history_length(&mut self, history_length: usize) -> &mut Self { - self.config.history_length = history_length; - self - } - - /// Number of past heartbeats to gossip about (default is 3). - pub fn history_gossip(&mut self, history_gossip: usize) -> &mut Self { - self.config.history_gossip = history_gossip; - self - } - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub fn mesh_n(&mut self, mesh_n: usize) -> &mut Self { - self.config.mesh_n = mesh_n; - self - } - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 4). - pub fn mesh_n_low(&mut self, mesh_n_low: usize) -> &mut Self { - self.config.mesh_n_low = mesh_n_low; - self - } - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub fn mesh_n_high(&mut self, mesh_n_high: usize) -> &mut Self { - self.config.mesh_n_high = mesh_n_high; - self - } - - /// Affects how peers are selected when pruning a mesh due to over subscription. - /// - /// At least [`Self::retain_scores`] of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). - pub fn retain_scores(&mut self, retain_scores: usize) -> &mut Self { - self.config.retain_scores = retain_scores; - self - } - - /// Minimum number of peers to emit gossip to during a heartbeat (D_lazy in the spec, - /// default is 6). - pub fn gossip_lazy(&mut self, gossip_lazy: usize) -> &mut Self { - self.config.gossip_lazy = gossip_lazy; - self - } - - /// Affects how many peers we will emit gossip to at each heartbeat. - /// - /// We will send gossip to `gossip_factor * (total number of non-mesh peers)`, or - /// `gossip_lazy`, whichever is greater. The default is 0.25. - pub fn gossip_factor(&mut self, gossip_factor: f64) -> &mut Self { - self.config.gossip_factor = gossip_factor; - self - } - - /// Initial delay in each heartbeat (default is 5 seconds). - pub fn heartbeat_initial_delay(&mut self, heartbeat_initial_delay: Duration) -> &mut Self { - self.config.heartbeat_initial_delay = heartbeat_initial_delay; - self - } - - /// Time between each heartbeat (default is 1 second). - pub fn heartbeat_interval(&mut self, heartbeat_interval: Duration) -> &mut Self { - self.config.heartbeat_interval = heartbeat_interval; - self - } - - /// The number of heartbeat ticks until we recheck the connection to explicit peers and - /// reconnecting if necessary (default 300). - pub fn check_explicit_peers_ticks(&mut self, check_explicit_peers_ticks: u64) -> &mut Self { - self.config.check_explicit_peers_ticks = check_explicit_peers_ticks; - self - } - - /// Time to live for fanout peers (default is 60 seconds). - pub fn fanout_ttl(&mut self, fanout_ttl: Duration) -> &mut Self { - self.config.fanout_ttl = fanout_ttl; - self - } - - /// The maximum byte size for each gossip (default is 2048 bytes). - pub fn max_transmit_size(&mut self, max_transmit_size: usize) -> &mut Self { - self.config.protocol.max_transmit_size = max_transmit_size; - self - } - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub fn duplicate_cache_time(&mut self, cache_size: Duration) -> &mut Self { - self.config.duplicate_cache_time = cache_size; - self - } - - /// When set, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set, - /// the user must manually call [`crate::Behaviour::report_message_validation_result()`] on the - /// behaviour to forward a message once validated. - pub fn validate_messages(&mut self) -> &mut Self { - self.config.validate_messages = true; - self - } - - /// Determines the level of validation used when receiving messages. See [`ValidationMode`] - /// for the available types. The default is ValidationMode::Strict. - pub fn validation_mode(&mut self, validation_mode: ValidationMode) -> &mut Self { - self.config.protocol.validation_mode = validation_mode; - self - } - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a [`Message`] as input and outputs a String to be - /// interpreted as the message id. - pub fn message_id_fn(&mut self, id_fn: F) -> &mut Self - where - F: Fn(&Message) -> MessageId + Send + Sync + 'static, - { - self.config.message_id_fn = Arc::new(id_fn); - self - } - - /// Enables Peer eXchange. This should be enabled in bootstrappers and other well - /// connected/trusted nodes. The default is false. - /// - /// Note: Peer exchange is not implemented today, see - /// . - pub fn do_px(&mut self) -> &mut Self { - self.config.do_px = true; - self - } - - /// Controls the number of peers to include in prune Peer eXchange. - /// - /// When we prune a peer that's eligible for PX (has a good score, etc), we will try to - /// send them signed peer records for up to [`Self::prune_peers] other peers that we - /// know of. It is recommended that this value is larger than [`Self::mesh_n_high`] so that the - /// pruned peer can reliably form a full mesh. The default is 16. - pub fn prune_peers(&mut self, prune_peers: usize) -> &mut Self { - self.config.prune_peers = prune_peers; - self - } - - /// Controls the backoff time for pruned peers. This is how long - /// a peer must wait before attempting to graft into our mesh again after being pruned. - /// When pruning a peer, we send them our value of [`Self::prune_backoff`] so they know - /// the minimum time to wait. Peers running older versions may not send a backoff time, - /// so if we receive a prune message without one, we will wait at least [`Self::prune_backoff`] - /// before attempting to re-graft. The default is one minute. - pub fn prune_backoff(&mut self, prune_backoff: Duration) -> &mut Self { - self.config.prune_backoff = prune_backoff; - self - } - - /// Controls the backoff time when unsubscribing from a topic. - /// - /// This is how long to wait before resubscribing to the topic. A short backoff period in case - /// of an unsubscribe event allows reaching a healthy mesh in a more timely manner. The default - /// is 10 seconds. - pub fn unsubscribe_backoff(&mut self, unsubscribe_backoff: u64) -> &mut Self { - self.config.unsubscribe_backoff = Duration::from_secs(unsubscribe_backoff); - self - } - - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait - /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if - /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing - /// prunes on our side and processing prunes on the receiving side this guarantees that we - /// get not punished for too early grafting. The default is 1. - pub fn backoff_slack(&mut self, backoff_slack: u32) -> &mut Self { - self.config.backoff_slack = backoff_slack; - self - } - - /// Whether to do flood publishing or not. If enabled newly created messages will always be - /// sent to all peers that are subscribed to the topic and have a good enough score. - /// The default is true. - pub fn flood_publish(&mut self, flood_publish: bool) -> &mut Self { - self.config.flood_publish = flood_publish; - self - } - - /// If a GRAFT comes before `graft_flood_threshold` has elapsed since the last PRUNE, - /// then there is an extra score penalty applied to the peer through P7. - pub fn graft_flood_threshold(&mut self, graft_flood_threshold: Duration) -> &mut Self { - self.config.graft_flood_threshold = graft_flood_threshold; - self - } - - /// Minimum number of outbound peers in the mesh network before adding more (D_out in the spec). - /// This value must be smaller or equal than `mesh_n / 2` and smaller than `mesh_n_low`. - /// The default is 2. - pub fn mesh_outbound_min(&mut self, mesh_outbound_min: usize) -> &mut Self { - self.config.mesh_outbound_min = mesh_outbound_min; - self - } - - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is - /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh - /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a - /// threshold (see ). - /// The default is 60. - pub fn opportunistic_graft_ticks(&mut self, opportunistic_graft_ticks: u64) -> &mut Self { - self.config.opportunistic_graft_ticks = opportunistic_graft_ticks; - self - } - - /// Controls how many times we will allow a peer to request the same message id through IWANT - /// gossip before we start ignoring them. This is designed to prevent peers from spamming us - /// with requests and wasting our resources. - pub fn gossip_retransimission(&mut self, gossip_retransimission: u32) -> &mut Self { - self.config.gossip_retransimission = gossip_retransimission; - self - } - - /// The maximum number of new peers to graft to during opportunistic grafting. The default is 2. - pub fn opportunistic_graft_peers(&mut self, opportunistic_graft_peers: usize) -> &mut Self { - self.config.opportunistic_graft_peers = opportunistic_graft_peers; - self - } - - /// The maximum number of messages we will process in a given RPC. If this is unset, there is - /// no limit. The default is None. - pub fn max_messages_per_rpc(&mut self, max: Option) -> &mut Self { - self.config.max_messages_per_rpc = max; - self - } - - /// The maximum number of messages to include in an IHAVE message. - /// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a - /// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the - /// default if your system is pushing more than 5000 messages in GossipSubHistoryGossip - /// heartbeats; with the defaults this is 1666 messages/s. The default is 5000. - pub fn max_ihave_length(&mut self, max_ihave_length: usize) -> &mut Self { - self.config.max_ihave_length = max_ihave_length; - self - } - - /// GossipSubMaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer - /// within a heartbeat. - pub fn max_ihave_messages(&mut self, max_ihave_messages: usize) -> &mut Self { - self.config.max_ihave_messages = max_ihave_messages; - self - } - - /// By default, gossipsub will reject messages that are sent to us that has the same message - /// source as we have specified locally. Enabling this, allows these messages and prevents - /// penalizing the peer that sent us the message. Default is false. - pub fn allow_self_origin(&mut self, allow_self_origin: bool) -> &mut Self { - self.config.allow_self_origin = allow_self_origin; - self - } - - /// Time to wait for a message requested through IWANT following an IHAVE advertisement. - /// If the message is not received within this window, a broken promise is declared and - /// the router may apply behavioural penalties. The default is 3 seconds. - pub fn iwant_followup_time(&mut self, iwant_followup_time: Duration) -> &mut Self { - self.config.iwant_followup_time = iwant_followup_time; - self - } - - /// Enable support for flooodsub peers. - pub fn support_floodsub(&mut self) -> &mut Self { - if self - .config - .protocol - .protocol_ids - .contains(&FLOODSUB_PROTOCOL) - { - return self; - } - - self.config.protocol.protocol_ids.push(FLOODSUB_PROTOCOL); - self - } - - /// Published message ids time cache duration. The default is 10 seconds. - pub fn published_message_ids_cache_time( - &mut self, - published_message_ids_cache_time: Duration, - ) -> &mut Self { - self.config.published_message_ids_cache_time = published_message_ids_cache_time; - self - } - - /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. - pub fn connection_handler_queue_len(&mut self, len: usize) -> &mut Self { - self.config.connection_handler_queue_len = len; - self - } - - /// The duration a message to be published can wait to be sent before it is abandoned. The - /// default is 5 seconds. - pub fn publish_queue_duration(&mut self, duration: Duration) -> &mut Self { - self.config.connection_handler_publish_duration = duration; - self - } - - /// The duration a message to be forwarded can wait to be sent before it is abandoned. The - /// default is 1s. - pub fn forward_queue_duration(&mut self, duration: Duration) -> &mut Self { - self.config.connection_handler_forward_duration = duration; - self - } - - // The message size threshold for which IDONTWANT messages are sent. - // Sending IDONTWANT messages for small messages can have a negative effect to the overall - // traffic and CPU load. This acts as a lower bound cutoff for the message size to which - // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 - // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) - // default is 1kB - pub fn idontwant_message_size_threshold(&mut self, size: usize) -> &mut Self { - self.config.idontwant_message_size_threshold = size; - self - } - - /// Constructs a [`Config`] from the given configuration and validates the settings. - pub fn build(&self) -> Result { - // check all constraints on config - - if self.config.protocol.max_transmit_size < 100 { - return Err(ConfigBuilderError::MaxTransmissionSizeTooSmall); - } - - if self.config.history_length < self.config.history_gossip { - return Err(ConfigBuilderError::HistoryLengthTooSmall); - } - - if !(self.config.mesh_outbound_min <= self.config.mesh_n_low - && self.config.mesh_n_low <= self.config.mesh_n - && self.config.mesh_n <= self.config.mesh_n_high) - { - return Err(ConfigBuilderError::MeshParametersInvalid); - } - - if self.config.mesh_outbound_min * 2 > self.config.mesh_n { - return Err(ConfigBuilderError::MeshOutboundInvalid); - } - - if self.config.unsubscribe_backoff.as_millis() == 0 { - return Err(ConfigBuilderError::UnsubscribeBackoffIsZero); - } - - if self.invalid_protocol { - return Err(ConfigBuilderError::InvalidProtocol); - } - - Ok(self.config.clone()) - } -} - -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut builder = f.debug_struct("GossipsubConfig"); - let _ = builder.field("protocol", &self.protocol); - let _ = builder.field("history_length", &self.history_length); - let _ = builder.field("history_gossip", &self.history_gossip); - let _ = builder.field("mesh_n", &self.mesh_n); - let _ = builder.field("mesh_n_low", &self.mesh_n_low); - let _ = builder.field("mesh_n_high", &self.mesh_n_high); - let _ = builder.field("retain_scores", &self.retain_scores); - let _ = builder.field("gossip_lazy", &self.gossip_lazy); - let _ = builder.field("gossip_factor", &self.gossip_factor); - let _ = builder.field("heartbeat_initial_delay", &self.heartbeat_initial_delay); - let _ = builder.field("heartbeat_interval", &self.heartbeat_interval); - let _ = builder.field("fanout_ttl", &self.fanout_ttl); - let _ = builder.field("duplicate_cache_time", &self.duplicate_cache_time); - let _ = builder.field("validate_messages", &self.validate_messages); - let _ = builder.field("allow_self_origin", &self.allow_self_origin); - let _ = builder.field("do_px", &self.do_px); - let _ = builder.field("prune_peers", &self.prune_peers); - let _ = builder.field("prune_backoff", &self.prune_backoff); - let _ = builder.field("backoff_slack", &self.backoff_slack); - let _ = builder.field("flood_publish", &self.flood_publish); - let _ = builder.field("graft_flood_threshold", &self.graft_flood_threshold); - let _ = builder.field("mesh_outbound_min", &self.mesh_outbound_min); - let _ = builder.field("opportunistic_graft_ticks", &self.opportunistic_graft_ticks); - let _ = builder.field("opportunistic_graft_peers", &self.opportunistic_graft_peers); - let _ = builder.field("max_messages_per_rpc", &self.max_messages_per_rpc); - let _ = builder.field("max_ihave_length", &self.max_ihave_length); - let _ = builder.field("max_ihave_messages", &self.max_ihave_messages); - let _ = builder.field("iwant_followup_time", &self.iwant_followup_time); - let _ = builder.field( - "published_message_ids_cache_time", - &self.published_message_ids_cache_time, - ); - let _ = builder.field( - "idontwant_message_size_threhold", - &self.idontwant_message_size_threshold, - ); - builder.finish() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::topic::IdentityHash; - use crate::Topic; - use libp2p::core::UpgradeInfo; - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - #[test] - fn create_config_with_message_id_as_plain_function() { - let config = ConfigBuilder::default() - .message_id_fn(message_id_plain_function) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_message_id_as_closure() { - let config = ConfigBuilder::default() - .message_id_fn(|message: &Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push('e'); - MessageId::from(v) - }) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_message_id_as_closure_with_variable_capture() { - let captured: char = 'e'; - - let config = ConfigBuilder::default() - .message_id_fn(move |message: &Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push(captured); - MessageId::from(v) - }) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_protocol_id_prefix() { - let protocol_config = ConfigBuilder::default() - .protocol_id_prefix("/purple") - .build() - .unwrap() - .protocol_config(); - - let protocol_ids = protocol_config.protocol_info(); - - assert_eq!(protocol_ids.len(), 2); - - assert_eq!( - protocol_ids[0].protocol, - StreamProtocol::new("/purple/1.1.0") - ); - assert_eq!(protocol_ids[0].kind, PeerKind::Gossipsubv1_1); - - assert_eq!( - protocol_ids[1].protocol, - StreamProtocol::new("/purple/1.0.0") - ); - assert_eq!(protocol_ids[1].kind, PeerKind::Gossipsub); - } - - #[test] - fn create_config_with_custom_protocol_id() { - let protocol_config = ConfigBuilder::default() - .protocol_id("/purple", Version::V1_0) - .build() - .unwrap() - .protocol_config(); - - let protocol_ids = protocol_config.protocol_info(); - - assert_eq!(protocol_ids.len(), 1); - - assert_eq!(protocol_ids[0].protocol, "/purple"); - assert_eq!(protocol_ids[0].kind, PeerKind::Gossipsub); - } - - fn get_gossipsub_message() -> Message { - Message { - source: None, - data: vec![12, 34, 56], - sequence_number: None, - topic: Topic::::new("test").hash(), - } - } - - fn get_expected_message_id() -> MessageId { - MessageId::from([ - 49, 55, 56, 51, 56, 52, 49, 51, 52, 51, 52, 55, 51, 51, 53, 52, 54, 54, 52, 49, 101, - ]) - } - - fn message_id_plain_function(message: &Message) -> MessageId { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push('e'); - MessageId::from(v) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/error.rs b/beacon_node/lighthouse_network/gossipsub/src/error.rs deleted file mode 100644 index df3332bc92..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/error.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Error types that can result from gossipsub. - -use libp2p::identity::SigningError; - -/// Error associated with publishing a gossipsub message. -#[derive(Debug)] -pub enum PublishError { - /// This message has already been published. - Duplicate, - /// An error occurred whilst signing the message. - SigningError(SigningError), - /// There were no peers to send this message to. - InsufficientPeers, - /// The overall message was too large. This could be due to excessive topics or an excessive - /// message size. - MessageTooLarge, - /// The compression algorithm failed. - TransformFailed(std::io::Error), - /// Messages could not be sent because all queues for peers were full. The usize represents the - /// number of peers that have full queues. - AllQueuesFull(usize), -} - -impl std::fmt::Display for PublishError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for PublishError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::SigningError(err) => Some(err), - Self::TransformFailed(err) => Some(err), - _ => None, - } - } -} - -/// Error associated with subscribing to a topic. -#[derive(Debug)] -pub enum SubscriptionError { - /// Couldn't publish our subscription - PublishError(PublishError), - /// We are not allowed to subscribe to this topic by the subscription filter - NotAllowed, -} - -impl std::fmt::Display for SubscriptionError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for SubscriptionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::PublishError(err) => Some(err), - _ => None, - } - } -} - -impl From for PublishError { - fn from(error: SigningError) -> Self { - PublishError::SigningError(error) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum ValidationError { - /// The message has an invalid signature, - InvalidSignature, - /// The sequence number was empty, expected a value. - EmptySequenceNumber, - /// The sequence number was the incorrect size - InvalidSequenceNumber, - /// The PeerId was invalid - InvalidPeerId, - /// Signature existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SignaturePresent, - /// Sequence number existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SequenceNumberPresent, - /// Message source existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - MessageSourcePresent, - /// The data transformation failed. - TransformFailed, -} - -impl std::fmt::Display for ValidationError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for ValidationError {} - -impl From for PublishError { - fn from(error: std::io::Error) -> PublishError { - PublishError::TransformFailed(error) - } -} - -/// Error associated with Config building. -#[derive(Debug)] -pub enum ConfigBuilderError { - /// Maximum transmission size is too small. - MaxTransmissionSizeTooSmall, - /// Histroy length less than history gossip length. - HistoryLengthTooSmall, - /// The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high - MeshParametersInvalid, - /// The inequality doesn't hold mesh_outbound_min <= self.config.mesh_n / 2 - MeshOutboundInvalid, - /// unsubscribe_backoff is zero - UnsubscribeBackoffIsZero, - /// Invalid protocol - InvalidProtocol, -} - -impl std::error::Error for ConfigBuilderError {} - -impl std::fmt::Display for ConfigBuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::MaxTransmissionSizeTooSmall => { - write!(f, "Maximum transmission size is too small") - } - Self::HistoryLengthTooSmall => write!(f, "Histroy length less than history gossip length"), - Self::MeshParametersInvalid => write!(f, "The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high"), - Self::MeshOutboundInvalid => write!(f, "The inequality doesn't hold mesh_outbound_min <= self.config.mesh_n / 2"), - Self::UnsubscribeBackoffIsZero => write!(f, "unsubscribe_backoff is zero"), - Self::InvalidProtocol => write!(f, "Invalid protocol"), - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto b/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto deleted file mode 100644 index b2753bf7e4..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto2"; - -package compat.pb; - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; - optional bytes signature = 5; - optional bytes key = 6; -} \ No newline at end of file diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs deleted file mode 100644 index aec6164c7e..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod pb; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs deleted file mode 100644 index fd59c38e2b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Automatically generated rust module for 'compat.proto' file - -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(unused_imports)] -#![allow(unknown_lints)] -#![allow(clippy::all)] -#![cfg_attr(rustfmt, rustfmt_skip)] - - -use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; -use quick_protobuf::sizeofs::*; -use super::super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Message { - pub from: Option>, - pub data: Option>, - pub seqno: Option>, - pub topic_ids: Vec, - pub signature: Option>, - pub key: Option>, -} - -impl<'a> MessageRead<'a> for Message { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.from = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.data = Some(r.read_bytes(bytes)?.to_owned()), - Ok(26) => msg.seqno = Some(r.read_bytes(bytes)?.to_owned()), - Ok(34) => msg.topic_ids.push(r.read_string(bytes)?.to_owned()), - Ok(42) => msg.signature = Some(r.read_bytes(bytes)?.to_owned()), - Ok(50) => msg.key = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for Message { - fn get_size(&self) -> usize { - 0 - + self.from.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.data.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.seqno.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.topic_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - + self.signature.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.key.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.from { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.data { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.seqno { w.write_with_tag(26, |w| w.write_bytes(&**s))?; } - for s in &self.topic_ids { w.write_with_tag(34, |w| w.write_string(&**s))?; } - if let Some(ref s) = self.signature { w.write_with_tag(42, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.key { w.write_with_tag(50, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs deleted file mode 100644 index aec6164c7e..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod pb; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs deleted file mode 100644 index 24ac80d275..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs +++ /dev/null @@ -1,603 +0,0 @@ -// Automatically generated rust module for 'rpc.proto' file - -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(unused_imports)] -#![allow(unknown_lints)] -#![allow(clippy::all)] -#![cfg_attr(rustfmt, rustfmt_skip)] - - -use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; -use quick_protobuf::sizeofs::*; -use super::super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct RPC { - pub subscriptions: Vec, - pub publish: Vec, - pub control: Option, -} - -impl<'a> MessageRead<'a> for RPC { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.subscriptions.push(r.read_message::(bytes)?), - Ok(18) => msg.publish.push(r.read_message::(bytes)?), - Ok(26) => msg.control = Some(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for RPC { - fn get_size(&self) -> usize { - 0 - + self.subscriptions.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.publish.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.control.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.subscriptions { w.write_with_tag(10, |w| w.write_message(s))?; } - for s in &self.publish { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.control { w.write_with_tag(26, |w| w.write_message(s))?; } - Ok(()) - } -} - -pub mod mod_RPC { - -use super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct SubOpts { - pub subscribe: Option, - pub topic_id: Option, -} - -impl<'a> MessageRead<'a> for SubOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.subscribe = Some(r.read_bool(bytes)?), - Ok(18) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for SubOpts { - fn get_size(&self) -> usize { - 0 - + self.subscribe.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.subscribe { w.write_with_tag(8, |w| w.write_bool(*s))?; } - if let Some(ref s) = self.topic_id { w.write_with_tag(18, |w| w.write_string(&**s))?; } - Ok(()) - } -} - -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Message { - pub from: Option>, - pub data: Option>, - pub seqno: Option>, - pub topic: String, - pub signature: Option>, - pub key: Option>, -} - -impl<'a> MessageRead<'a> for Message { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.from = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.data = Some(r.read_bytes(bytes)?.to_owned()), - Ok(26) => msg.seqno = Some(r.read_bytes(bytes)?.to_owned()), - Ok(34) => msg.topic = r.read_string(bytes)?.to_owned(), - Ok(42) => msg.signature = Some(r.read_bytes(bytes)?.to_owned()), - Ok(50) => msg.key = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for Message { - fn get_size(&self) -> usize { - 0 - + self.from.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.data.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.seqno.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + 1 + sizeof_len((&self.topic).len()) - + self.signature.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.key.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.from { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.data { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.seqno { w.write_with_tag(26, |w| w.write_bytes(&**s))?; } - w.write_with_tag(34, |w| w.write_string(&**&self.topic))?; - if let Some(ref s) = self.signature { w.write_with_tag(42, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.key { w.write_with_tag(50, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlMessage { - pub ihave: Vec, - pub iwant: Vec, - pub graft: Vec, - pub prune: Vec, - pub idontwant: Vec, -} - -impl<'a> MessageRead<'a> for ControlMessage { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.ihave.push(r.read_message::(bytes)?), - Ok(18) => msg.iwant.push(r.read_message::(bytes)?), - Ok(26) => msg.graft.push(r.read_message::(bytes)?), - Ok(34) => msg.prune.push(r.read_message::(bytes)?), - Ok(42) => msg.idontwant.push(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlMessage { - fn get_size(&self) -> usize { - 0 - + self.ihave.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.iwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.graft.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.prune.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.idontwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.ihave { w.write_with_tag(10, |w| w.write_message(s))?; } - for s in &self.iwant { w.write_with_tag(18, |w| w.write_message(s))?; } - for s in &self.graft { w.write_with_tag(26, |w| w.write_message(s))?; } - for s in &self.prune { w.write_with_tag(34, |w| w.write_message(s))?; } - for s in &self.idontwant { w.write_with_tag(42, |w| w.write_message(s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIHave { - pub topic_id: Option, - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIHave { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIHave { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - for s in &self.message_ids { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIWant { - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIWant { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIWant { - fn get_size(&self) -> usize { - 0 - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.message_ids { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlGraft { - pub topic_id: Option, -} - -impl<'a> MessageRead<'a> for ControlGraft { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlGraft { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlPrune { - pub topic_id: Option, - pub peers: Vec, - pub backoff: Option, -} - -impl<'a> MessageRead<'a> for ControlPrune { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.peers.push(r.read_message::(bytes)?), - Ok(24) => msg.backoff = Some(r.read_uint64(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlPrune { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.peers.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.backoff.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - for s in &self.peers { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.backoff { w.write_with_tag(24, |w| w.write_uint64(*s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIDontWant { - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIDontWant { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIDontWant { - fn get_size(&self) -> usize { - 0 - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.message_ids { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct PeerInfo { - pub peer_id: Option>, - pub signed_peer_record: Option>, -} - -impl<'a> MessageRead<'a> for PeerInfo { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.peer_id = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.signed_peer_record = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for PeerInfo { - fn get_size(&self) -> usize { - 0 - + self.peer_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.signed_peer_record.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.peer_id { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.signed_peer_record { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct TopicDescriptor { - pub name: Option, - pub auth: Option, - pub enc: Option, -} - -impl<'a> MessageRead<'a> for TopicDescriptor { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.name = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.auth = Some(r.read_message::(bytes)?), - Ok(26) => msg.enc = Some(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for TopicDescriptor { - fn get_size(&self) -> usize { - 0 - + self.name.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.auth.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - + self.enc.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.name { w.write_with_tag(10, |w| w.write_string(&**s))?; } - if let Some(ref s) = self.auth { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.enc { w.write_with_tag(26, |w| w.write_message(s))?; } - Ok(()) - } -} - -pub mod mod_TopicDescriptor { - -use super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct AuthOpts { - pub mode: Option, - pub keys: Vec>, -} - -impl<'a> MessageRead<'a> for AuthOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.mode = Some(r.read_enum(bytes)?), - Ok(18) => msg.keys.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for AuthOpts { - fn get_size(&self) -> usize { - 0 - + self.mode.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.keys.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.mode { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } - for s in &self.keys { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -pub mod mod_AuthOpts { - - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum AuthMode { - NONE = 0, - KEY = 1, - WOT = 2, -} - -impl Default for AuthMode { - fn default() -> Self { - AuthMode::NONE - } -} - -impl From for AuthMode { - fn from(i: i32) -> Self { - match i { - 0 => AuthMode::NONE, - 1 => AuthMode::KEY, - 2 => AuthMode::WOT, - _ => Self::default(), - } - } -} - -impl<'a> From<&'a str> for AuthMode { - fn from(s: &'a str) -> Self { - match s { - "NONE" => AuthMode::NONE, - "KEY" => AuthMode::KEY, - "WOT" => AuthMode::WOT, - _ => Self::default(), - } - } -} - -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct EncOpts { - pub mode: Option, - pub key_hashes: Vec>, -} - -impl<'a> MessageRead<'a> for EncOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.mode = Some(r.read_enum(bytes)?), - Ok(18) => msg.key_hashes.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for EncOpts { - fn get_size(&self) -> usize { - 0 - + self.mode.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.key_hashes.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.mode { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } - for s in &self.key_hashes { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -pub mod mod_EncOpts { - - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum EncMode { - NONE = 0, - SHAREDKEY = 1, - WOT = 2, -} - -impl Default for EncMode { - fn default() -> Self { - EncMode::NONE - } -} - -impl From for EncMode { - fn from(i: i32) -> Self { - match i { - 0 => EncMode::NONE, - 1 => EncMode::SHAREDKEY, - 2 => EncMode::WOT, - _ => Self::default(), - } - } -} - -impl<'a> From<&'a str> for EncMode { - fn from(s: &'a str) -> Self { - match s { - "NONE" => EncMode::NONE, - "SHAREDKEY" => EncMode::SHAREDKEY, - "WOT" => EncMode::WOT, - _ => Self::default(), - } - } -} - -} - -} - diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs deleted file mode 100644 index 7ac564f3c3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Automatically generated mod.rs -pub mod compat; -pub mod gossipsub; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto b/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto deleted file mode 100644 index e3b5888d2c..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto +++ /dev/null @@ -1,89 +0,0 @@ -syntax = "proto2"; - -package gossipsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubscribe - optional string topic_id = 2; - } - - optional ControlMessage control = 3; -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - required string topic = 4; - optional bytes signature = 5; - optional bytes key = 6; -} - -message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - repeated ControlIDontWant idontwant = 5; -} - -message ControlIHave { - optional string topic_id = 1; - repeated bytes message_ids = 2; -} - -message ControlIWant { - repeated bytes message_ids= 1; -} - -message ControlGraft { - optional string topic_id = 1; -} - -message ControlPrune { - optional string topic_id = 1; - repeated PeerInfo peers = 2; // gossipsub v1.1 PX - optional uint64 backoff = 3; // gossipsub v1.1 backoff time (in seconds) -} - -message ControlIDontWant { - repeated bytes message_ids = 1; -} - -message PeerInfo { - optional bytes peer_id = 1; - optional bytes signed_peer_record = 2; -} - -// topicID = hash(topicDescriptor); (not the topic.name) -message TopicDescriptor { - optional string name = 1; - optional AuthOpts auth = 2; - optional EncOpts enc = 3; - - message AuthOpts { - optional AuthMode mode = 1; - repeated bytes keys = 2; // root keys to trust - - enum AuthMode { - NONE = 0; // no authentication, anyone can publish - KEY = 1; // only messages signed by keys in the topic descriptor are accepted - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } - - message EncOpts { - optional EncMode mode = 1; - repeated bytes key_hashes = 2; // the hashes of the shared keys used (salted) - - enum EncMode { - NONE = 0; // no encryption, anyone can read - SHAREDKEY = 1; // messages are encrypted with shared key - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs b/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs deleted file mode 100644 index ce1dee2a72..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::peer_score::RejectReason; -use super::MessageId; -use super::ValidationError; -use libp2p::identity::PeerId; -use std::collections::HashMap; -use web_time::Instant; - -/// Tracks recently sent `IWANT` messages and checks if peers respond to them. -#[derive(Default)] -pub(crate) struct GossipPromises { - /// Stores for each tracked message id and peer the instant when this promise expires. - /// - /// If the peer didn't respond until then we consider the promise as broken and penalize the - /// peer. - promises: HashMap>, -} - -impl GossipPromises { - /// Returns true if the message id exists in the promises. - pub(crate) fn contains(&self, message: &MessageId) -> bool { - self.promises.contains_key(message) - } - - /// Returns true if the message id exists in the promises and contains the given peer. - pub(crate) fn contains_peer(&self, message: &MessageId, peer: &PeerId) -> bool { - self.promises - .get(message) - .is_some_and(|peers| peers.contains_key(peer)) - } - - ///Get the peers we sent IWANT the input message id. - pub(crate) fn peers_for_message(&self, message_id: &MessageId) -> Vec { - self.promises - .get(message_id) - .map(|peers| peers.keys().copied().collect()) - .unwrap_or_default() - } - - /// Track a promise to deliver a message from a list of [`MessageId`]s we are requesting. - pub(crate) fn add_promise(&mut self, peer: PeerId, messages: &[MessageId], expires: Instant) { - for message_id in messages { - // If a promise for this message id and peer already exists we don't update the expiry! - self.promises - .entry(message_id.clone()) - .or_default() - .entry(peer) - .or_insert(expires); - } - } - - pub(crate) fn message_delivered(&mut self, message_id: &MessageId) { - // Someone delivered a message, we can stop tracking all promises for it. - self.promises.remove(message_id); - } - - pub(crate) fn reject_message(&mut self, message_id: &MessageId, reason: &RejectReason) { - // A message got rejected, so we can stop tracking promises and let the score penalty apply - // from invalid message delivery. - // We do take exception and apply promise penalty regardless in the following cases, where - // the peer delivered an obviously invalid message. - match reason { - RejectReason::ValidationError(ValidationError::InvalidSignature) => (), - RejectReason::SelfOrigin => (), - _ => { - self.promises.remove(message_id); - } - }; - } - - /// Returns the number of broken promises for each peer who didn't follow up on an IWANT - /// request. - /// This should be called not too often relative to the expire times, since it iterates over - /// the whole stored data. - pub(crate) fn get_broken_promises(&mut self) -> HashMap { - let now = Instant::now(); - let mut result = HashMap::new(); - self.promises.retain(|msg, peers| { - peers.retain(|peer_id, expires| { - if *expires < now { - let count = result.entry(*peer_id).or_insert(0); - *count += 1; - tracing::debug!( - peer=%peer_id, - message=%msg, - "[Penalty] The peer broke the promise to deliver message in time!" - ); - false - } else { - true - } - }); - !peers.is_empty() - }); - result - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/handler.rs b/beacon_node/lighthouse_network/gossipsub/src/handler.rs deleted file mode 100644 index 0f25db6e3d..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/handler.rs +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::protocol::{GossipsubCodec, ProtocolConfig}; -use super::rpc_proto::proto; -use super::types::{PeerKind, RawMessage, Rpc, RpcOut, RpcReceiver}; -use super::ValidationError; -use asynchronous_codec::Framed; -use futures::future::Either; -use futures::prelude::*; -use futures::StreamExt; -use libp2p::core::upgrade::DeniedUpgrade; -use libp2p::swarm::handler::{ - ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, -}; -use libp2p::swarm::Stream; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; -use web_time::Instant; - -/// The event emitted by the Handler. This informs the behaviour of various events created -/// by the handler. -#[derive(Debug)] -pub enum HandlerEvent { - /// A GossipsubRPC message has been received. This also contains a list of invalid messages (if - /// any) that were received. - Message { - /// The GossipsubRPC message excluding any invalid messages. - rpc: Rpc, - /// Any invalid messages that were received in the RPC, along with the associated - /// validation error. - invalid_messages: Vec<(RawMessage, ValidationError)>, - }, - /// An inbound or outbound substream has been established with the peer and this informs over - /// which protocol. This message only occurs once per connection. - PeerKind(PeerKind), - /// A message to be published was dropped because it could not be sent in time. - MessageDropped(RpcOut), -} - -/// A message sent from the behaviour to the handler. -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -pub enum HandlerIn { - /// The peer has joined the mesh. - JoinedMesh, - /// The peer has left the mesh. - LeftMesh, -} - -/// The maximum number of inbound or outbound substreams attempts we allow. -/// -/// Gossipsub is supposed to have a single long-lived inbound and outbound substream. On failure we -/// attempt to recreate these. This imposes an upper bound of new substreams before we consider the -/// connection faulty and disable the handler. This also prevents against potential substream -/// creation loops. -const MAX_SUBSTREAM_ATTEMPTS: usize = 5; - -#[allow(clippy::large_enum_variant)] -pub enum Handler { - Enabled(EnabledHandler), - Disabled(DisabledHandler), -} - -/// Protocol Handler that manages a single long-lived substream with a peer. -pub struct EnabledHandler { - /// Upgrade configuration for the gossipsub protocol. - listen_protocol: ProtocolConfig, - - /// The single long-lived outbound substream. - outbound_substream: Option, - - /// The single long-lived inbound substream. - inbound_substream: Option, - - /// Queue of values that we want to send to the remote - send_queue: RpcReceiver, - - /// Flag indicating that an outbound substream is being established to prevent duplicate - /// requests. - outbound_substream_establishing: bool, - - /// The number of outbound substreams we have requested. - outbound_substream_attempts: usize, - - /// The number of inbound substreams that have been created by the peer. - inbound_substream_attempts: usize, - - /// The type of peer this handler is associated to. - peer_kind: Option, - - /// Keeps track on whether we have sent the peer kind to the behaviour. - // - // NOTE: Use this flag rather than checking the substream count each poll. - peer_kind_sent: bool, - - last_io_activity: Instant, - - /// Keeps track of whether this connection is for a peer in the mesh. This is used to make - /// decisions about the keep alive state for this connection. - in_mesh: bool, -} - -pub enum DisabledHandler { - /// If the peer doesn't support the gossipsub protocol we do not immediately disconnect. - /// Rather, we disable the handler and prevent any incoming or outgoing substreams from being - /// established. - ProtocolUnsupported { - /// Keeps track on whether we have sent the peer kind to the behaviour. - peer_kind_sent: bool, - }, - /// The maximum number of inbound or outbound substream attempts have happened and thereby the - /// handler has been disabled. - MaxSubstreamAttempts, -} - -/// State of the inbound substream, opened either by us or by the remote. -enum InboundSubstreamState { - /// Waiting for a message from the remote. The idle state for an inbound substream. - WaitingInput(Framed), - /// The substream is being closed. - Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -/// State of the outbound substream, opened either by us or by the remote. -enum OutboundSubstreamState { - /// Waiting for the user to send a message. The idle state for an outbound substream. - WaitingOutput(Framed), - /// Waiting to send a message to the remote. - PendingSend(Framed, proto::RPC), - /// Waiting to flush the substream so that the data arrives to the remote. - PendingFlush(Framed), - /// An error occurred during processing. - Poisoned, -} - -impl Handler { - /// Builds a new [`Handler`]. - pub fn new(protocol_config: ProtocolConfig, message_queue: RpcReceiver) -> Self { - Handler::Enabled(EnabledHandler { - listen_protocol: protocol_config, - inbound_substream: None, - outbound_substream: None, - outbound_substream_establishing: false, - outbound_substream_attempts: 0, - inbound_substream_attempts: 0, - peer_kind: None, - peer_kind_sent: false, - last_io_activity: Instant::now(), - in_mesh: false, - send_queue: message_queue, - }) - } -} - -impl EnabledHandler { - fn on_fully_negotiated_inbound( - &mut self, - (substream, peer_kind): (Framed, PeerKind), - ) { - // update the known kind of peer - if self.peer_kind.is_none() { - self.peer_kind = Some(peer_kind); - } - - // new inbound substream. Replace the current one, if it exists. - tracing::trace!("New inbound substream request"); - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - } - - fn on_fully_negotiated_outbound( - &mut self, - FullyNegotiatedOutbound { protocol, .. }: FullyNegotiatedOutbound< - ::OutboundProtocol, - >, - ) { - let (substream, peer_kind) = protocol; - - // update the known kind of peer - if self.peer_kind.is_none() { - self.peer_kind = Some(peer_kind); - } - - assert!( - self.outbound_substream.is_none(), - "Established an outbound substream with one already available" - ); - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll< - ConnectionHandlerEvent< - ::OutboundProtocol, - (), - ::ToBehaviour, - >, - > { - if !self.peer_kind_sent { - if let Some(peer_kind) = self.peer_kind.as_ref() { - self.peer_kind_sent = true; - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::PeerKind(peer_kind.clone()), - )); - } - } - - // determine if we need to create the outbound stream - if !self.send_queue.poll_is_empty(cx) - && self.outbound_substream.is_none() - && !self.outbound_substream_establishing - { - self.outbound_substream_establishing = true; - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(self.listen_protocol.clone(), ()), - }); - } - - // process outbound stream - loop { - match std::mem::replace( - &mut self.outbound_substream, - Some(OutboundSubstreamState::Poisoned), - ) { - // outbound idle state - Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if let Poll::Ready(Some(mut message)) = self.send_queue.poll_next_unpin(cx) { - match message { - RpcOut::Publish { - message: _, - ref mut timeout, - } - | RpcOut::Forward { - message: _, - ref mut timeout, - } => { - if Pin::new(timeout).poll(cx).is_ready() { - // Inform the behaviour and end the poll. - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)); - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::MessageDropped(message), - )); - } - } - _ => {} // All other messages are not time-bound. - } - self.outbound_substream = Some(OutboundSubstreamState::PendingSend( - substream, - message.into_protobuf(), - )); - continue; - } - - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)); - break; - } - Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { - match Sink::poll_ready(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - match Sink::start_send(Pin::new(&mut substream), message) { - Ok(()) => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingFlush(substream)) - } - Err(e) => { - tracing::debug!( - "Failed to send message on outbound stream: {e}" - ); - self.outbound_substream = None; - break; - } - } - } - Poll::Ready(Err(e)) => { - tracing::debug!("Failed to send message on outbound stream: {e}"); - self.outbound_substream = None; - break; - } - Poll::Pending => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingSend(substream, message)); - break; - } - } - } - Some(OutboundSubstreamState::PendingFlush(mut substream)) => { - match Sink::poll_flush(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.last_io_activity = Instant::now(); - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)) - } - Poll::Ready(Err(e)) => { - tracing::debug!("Failed to flush outbound stream: {e}"); - self.outbound_substream = None; - break; - } - Poll::Pending => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingFlush(substream)); - break; - } - } - } - None => { - self.outbound_substream = None; - break; - } - Some(OutboundSubstreamState::Poisoned) => { - unreachable!("Error occurred during outbound stream processing") - } - } - } - - // Handle inbound messages. - loop { - match std::mem::replace( - &mut self.inbound_substream, - Some(InboundSubstreamState::Poisoned), - ) { - // inbound idle state - Some(InboundSubstreamState::WaitingInput(mut substream)) => { - match substream.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(message))) => { - self.last_io_activity = Instant::now(); - self.inbound_substream = - Some(InboundSubstreamState::WaitingInput(substream)); - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(message)); - } - Poll::Ready(Some(Err(error))) => { - tracing::debug!("Failed to read from inbound stream: {error}"); - // Close this side of the stream. If the - // peer is still around, they will re-establish their - // outbound stream i.e. our inbound stream. - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - } - // peer closed the stream - Poll::Ready(None) => { - tracing::debug!("Inbound stream closed by remote"); - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - } - Poll::Pending => { - self.inbound_substream = - Some(InboundSubstreamState::WaitingInput(substream)); - break; - } - } - } - Some(InboundSubstreamState::Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(res) => { - if let Err(e) = res { - // Don't close the connection but just drop the inbound substream. - // In case the remote has more to send, they will open up a new - // substream. - tracing::debug!("Inbound substream error while closing: {e}"); - } - self.inbound_substream = None; - break; - } - Poll::Pending => { - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - break; - } - } - } - None => { - self.inbound_substream = None; - break; - } - Some(InboundSubstreamState::Poisoned) => { - unreachable!("Error occurred during inbound stream processing") - } - } - } - - // Drop the next message in queue if it's stale. - if let Poll::Ready(Some(rpc)) = self.send_queue.poll_stale(cx) { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::MessageDropped(rpc), - )); - } - - Poll::Pending - } -} - -impl ConnectionHandler for Handler { - type FromBehaviour = HandlerIn; - type ToBehaviour = HandlerEvent; - type InboundOpenInfo = (); - type InboundProtocol = either::Either; - type OutboundOpenInfo = (); - type OutboundProtocol = ProtocolConfig; - - fn listen_protocol(&self) -> SubstreamProtocol { - match self { - Handler::Enabled(handler) => { - SubstreamProtocol::new(either::Either::Left(handler.listen_protocol.clone()), ()) - } - Handler::Disabled(_) => { - SubstreamProtocol::new(either::Either::Right(DeniedUpgrade), ()) - } - } - } - - fn on_behaviour_event(&mut self, message: HandlerIn) { - match self { - Handler::Enabled(handler) => match message { - HandlerIn::JoinedMesh => { - handler.in_mesh = true; - } - HandlerIn::LeftMesh => { - handler.in_mesh = false; - } - }, - Handler::Disabled(_) => { - tracing::debug!(?message, "Handler is disabled. Dropping message"); - } - } - } - - fn connection_keep_alive(&self) -> bool { - matches!(self, Handler::Enabled(h) if h.in_mesh) - } - - #[tracing::instrument(level = "trace", name = "ConnectionHandler::poll", skip(self, cx))] - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll> { - match self { - Handler::Enabled(handler) => handler.poll(cx), - Handler::Disabled(DisabledHandler::ProtocolUnsupported { peer_kind_sent }) => { - if !*peer_kind_sent { - *peer_kind_sent = true; - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::PeerKind(PeerKind::NotSupported), - )); - } - - Poll::Pending - } - Handler::Disabled(DisabledHandler::MaxSubstreamAttempts) => Poll::Pending, - } - } - - fn on_connection_event( - &mut self, - event: ConnectionEvent, - ) { - match self { - Handler::Enabled(handler) => { - if event.is_inbound() { - handler.inbound_substream_attempts += 1; - - if handler.inbound_substream_attempts == MAX_SUBSTREAM_ATTEMPTS { - tracing::warn!( - "The maximum number of inbound substreams attempts has been exceeded" - ); - *self = Handler::Disabled(DisabledHandler::MaxSubstreamAttempts); - return; - } - } - - if event.is_outbound() { - handler.outbound_substream_establishing = false; - - handler.outbound_substream_attempts += 1; - - if handler.outbound_substream_attempts == MAX_SUBSTREAM_ATTEMPTS { - tracing::warn!( - "The maximum number of outbound substream attempts has been exceeded" - ); - *self = Handler::Disabled(DisabledHandler::MaxSubstreamAttempts); - return; - } - } - - match event { - ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { - protocol, - .. - }) => match protocol { - Either::Left(protocol) => handler.on_fully_negotiated_inbound(protocol), - #[allow(unreachable_patterns)] - Either::Right(v) => libp2p::core::util::unreachable(v), - }, - ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => { - handler.on_fully_negotiated_outbound(fully_negotiated_outbound) - } - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Timeout, - .. - }) => { - tracing::debug!("Dial upgrade error: Protocol negotiation timeout"); - } - // This pattern is unreachable as of Rust 1.82, we can remove it once the - // MSRV is increased past that version. - #[allow(unreachable_patterns)] - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Apply(e), - .. - }) => void::unreachable(e), - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::NegotiationFailed, - .. - }) => { - // The protocol is not supported - tracing::debug!( - "The remote peer does not support gossipsub on this connection" - ); - *self = Handler::Disabled(DisabledHandler::ProtocolUnsupported { - peer_kind_sent: false, - }); - } - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Io(e), - .. - }) => { - tracing::debug!("Protocol negotiation failed: {e}") - } - _ => {} - } - } - Handler::Disabled(_) => {} - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/lib.rs b/beacon_node/lighthouse_network/gossipsub/src/lib.rs deleted file mode 100644 index 1d29aaa759..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/lib.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implementation of the [Gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md) protocol. -//! -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! floodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! () provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence numbers in -//! this implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. When messages are signed, they are monotonically increasing integers starting from a -//! random value and wrapping around u64::MAX. When messages are unsigned, they are chosen at random. -//! NOTE: These numbers are sequential in the current go implementation. -//! -//! # Peer Discovery -//! -//! Gossipsub does not provide peer discovery by itself. Peer discovery is the process by which -//! peers in a p2p network exchange information about each other among other reasons to become resistant -//! against the failure or replacement of the -//! [boot nodes](https://docs.libp2p.io/reference/glossary/#boot-node) of the network. -//! -//! Peer -//! discovery can e.g. be implemented with the help of the [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) protocol -//! in combination with the [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol. See the -//! Kademlia implementation documentation for more information. -//! -//! # Using Gossipsub -//! -//! ## Gossipsub Config -//! -//! The [`Config`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`Config`]: struct.Config.html -//! -//! This struct implements the [`Default`] trait and can be initialised via -//! [`Config::default()`]. -//! -//! -//! ## Behaviour -//! -//! The [`Behaviour`] struct implements the [`libp2p_swarm::NetworkBehaviour`] trait allowing it to -//! act as the routing behaviour in a [`libp2p_swarm::Swarm`]. This struct requires an instance of -//! [`PeerId`](libp2p_identity::PeerId) and [`Config`]. -//! -//! [`Behaviour`]: struct.Behaviour.html - -//! ## Example -//! -//! For an example on how to use gossipsub, see the [chat-example](https://github.com/libp2p/rust-libp2p/tree/master/examples/chat). - -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -mod backoff; -mod behaviour; -mod config; -mod error; -mod gossip_promises; -mod handler; -mod mcache; -mod metrics; -mod peer_score; -mod protocol; -mod rpc_proto; -mod subscription_filter; -mod time_cache; -mod topic; -mod transform; -mod types; - -pub use self::behaviour::{Behaviour, Event, MessageAuthenticity}; -pub use self::config::{Config, ConfigBuilder, ValidationMode, Version}; -pub use self::error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}; -pub use self::metrics::Config as MetricsConfig; -pub use self::peer_score::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; -pub use self::subscription_filter::{ - AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, - MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, - WhitelistSubscriptionFilter, -}; -pub use self::topic::{Hasher, Topic, TopicHash}; -pub use self::transform::{DataTransform, IdentityTransform}; -pub use self::types::{FailedMessages, Message, MessageAcceptance, MessageId, RawMessage}; - -#[deprecated(note = "Will be removed from the public API.")] -pub type Rpc = self::types::Rpc; - -pub type IdentTopic = Topic; -pub type Sha256Topic = Topic; diff --git a/beacon_node/lighthouse_network/gossipsub/src/mcache.rs b/beacon_node/lighthouse_network/gossipsub/src/mcache.rs deleted file mode 100644 index eced0456d6..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/mcache.rs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::topic::TopicHash; -use super::types::{MessageId, RawMessage}; -use libp2p::identity::PeerId; -use std::collections::hash_map::Entry; -use std::fmt::Debug; -use std::{ - collections::{HashMap, HashSet}, - fmt, -}; - -/// CacheEntry stored in the history. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct CacheEntry { - mid: MessageId, - topic: TopicHash, -} - -/// MessageCache struct holding history of messages. -#[derive(Clone)] -pub(crate) struct MessageCache { - msgs: HashMap)>, - /// For every message and peer the number of times this peer asked for the message - iwant_counts: HashMap>, - history: Vec>, - /// The number of indices in the cache history used for gossiping. That means that a message - /// won't get gossiped anymore when shift got called `gossip` many times after inserting the - /// message in the cache. - gossip: usize, -} - -impl fmt::Debug for MessageCache { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MessageCache") - .field("msgs", &self.msgs) - .field("history", &self.history) - .field("gossip", &self.gossip) - .finish() - } -} - -/// Implementation of the MessageCache. -impl MessageCache { - pub(crate) fn new(gossip: usize, history_capacity: usize) -> Self { - MessageCache { - gossip, - msgs: HashMap::default(), - iwant_counts: HashMap::default(), - history: vec![Vec::new(); history_capacity], - } - } - - /// Put a message into the memory cache. - /// - /// Returns true if the message didn't already exist in the cache. - pub(crate) fn put(&mut self, message_id: &MessageId, msg: RawMessage) -> bool { - match self.msgs.entry(message_id.clone()) { - Entry::Occupied(_) => { - // Don't add duplicate entries to the cache. - false - } - Entry::Vacant(entry) => { - let cache_entry = CacheEntry { - mid: message_id.clone(), - topic: msg.topic.clone(), - }; - entry.insert((msg, HashSet::default())); - self.history[0].push(cache_entry); - - tracing::trace!(message=?message_id, "Put message in mcache"); - true - } - } - } - - /// Keeps track of peers we know have received the message to prevent forwarding to said peers. - pub(crate) fn observe_duplicate(&mut self, message_id: &MessageId, source: &PeerId) { - if let Some((message, originating_peers)) = self.msgs.get_mut(message_id) { - // if the message is already validated, we don't need to store extra peers sending us - // duplicates as the message has already been forwarded - if message.validated { - return; - } - - originating_peers.insert(*source); - } - } - - /// Get a message with `message_id` - #[cfg(test)] - pub(crate) fn get(&self, message_id: &MessageId) -> Option<&RawMessage> { - self.msgs.get(message_id).map(|(message, _)| message) - } - - /// Increases the iwant count for the given message by one and returns the message together - /// with the iwant if the message exists. - pub(crate) fn get_with_iwant_counts( - &mut self, - message_id: &MessageId, - peer: &PeerId, - ) -> Option<(&RawMessage, u32)> { - let iwant_counts = &mut self.iwant_counts; - self.msgs.get(message_id).and_then(|(message, _)| { - if !message.validated { - None - } else { - Some((message, { - let count = iwant_counts - .entry(message_id.clone()) - .or_default() - .entry(*peer) - .or_default(); - *count += 1; - *count - })) - } - }) - } - - /// Gets a message with [`MessageId`] and tags it as validated. - /// This function also returns the known peers that have sent us this message. This is used to - /// prevent us sending redundant messages to peers who have already propagated it. - pub(crate) fn validate( - &mut self, - message_id: &MessageId, - ) -> Option<(&RawMessage, HashSet)> { - self.msgs.get_mut(message_id).map(|(message, known_peers)| { - message.validated = true; - // Clear the known peers list (after a message is validated, it is forwarded and we no - // longer need to store the originating peers). - let originating_peers = std::mem::take(known_peers); - (&*message, originating_peers) - }) - } - - /// Get a list of [`MessageId`]s for a given topic. - pub(crate) fn get_gossip_message_ids(&self, topic: &TopicHash) -> Vec { - self.history[..self.gossip] - .iter() - .fold(vec![], |mut current_entries, entries| { - // search for entries with desired topic - let mut found_entries: Vec = entries - .iter() - .filter_map(|entry| { - if &entry.topic == topic { - let mid = &entry.mid; - // Only gossip validated messages - if let Some(true) = self.msgs.get(mid).map(|(msg, _)| msg.validated) { - Some(mid.clone()) - } else { - None - } - } else { - None - } - }) - .collect(); - - // generate the list - current_entries.append(&mut found_entries); - current_entries - }) - } - - /// Shift the history array down one and delete messages associated with the - /// last entry. - pub(crate) fn shift(&mut self) { - for entry in self.history.pop().expect("history is always > 1") { - if let Some((msg, _)) = self.msgs.remove(&entry.mid) { - if !msg.validated { - // If GossipsubConfig::validate_messages is true, the implementing - // application has to ensure that Gossipsub::validate_message gets called for - // each received message within the cache timeout time." - tracing::debug!( - message=%&entry.mid, - "The message got removed from the cache without being validated." - ); - } - } - tracing::trace!(message=%&entry.mid, "Remove message from the cache"); - - self.iwant_counts.remove(&entry.mid); - } - - // Insert an empty vec in position 0 - self.history.insert(0, Vec::new()); - } - - /// Removes a message from the cache and returns it if existent - pub(crate) fn remove( - &mut self, - message_id: &MessageId, - ) -> Option<(RawMessage, HashSet)> { - //We only remove the message from msgs and iwant_count and keep the message_id in the - // history vector. Zhe id in the history vector will simply be ignored on popping. - - self.iwant_counts.remove(message_id); - self.msgs.remove(message_id) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::IdentTopic as Topic; - - fn gen_testm(x: u64, topic: TopicHash) -> (MessageId, RawMessage) { - let default_id = |message: &RawMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.as_ref().unwrap().to_base58(); - source_string.push_str(&message.sequence_number.unwrap().to_string()); - MessageId::from(source_string) - }; - let u8x: u8 = x as u8; - let source = Some(PeerId::random()); - let data: Vec = vec![u8x]; - let sequence_number = Some(x); - - let m = RawMessage { - source, - data, - sequence_number, - topic, - signature: None, - key: None, - validated: false, - }; - - let id = default_id(&m); - (id, m) - } - - fn new_cache(gossip_size: usize, history: usize) -> MessageCache { - MessageCache::new(gossip_size, history) - } - - #[test] - /// Test that the message cache can be created. - fn test_new_cache() { - let x: usize = 3; - let mc = new_cache(x, 5); - - assert_eq!(mc.gossip, x); - } - - #[test] - /// Test you can put one message and get one. - fn test_put_get_one() { - let mut mc = new_cache(10, 15); - - let topic1_hash = Topic::new("topic1").hash(); - let (id, m) = gen_testm(10, topic1_hash); - - mc.put(&id, m.clone()); - - assert_eq!(mc.history[0].len(), 1); - - let fetched = mc.get(&id); - - assert_eq!(fetched.unwrap(), &m); - } - - #[test] - /// Test attempting to 'get' with a wrong id. - fn test_get_wrong() { - let mut mc = new_cache(10, 15); - - let topic1_hash = Topic::new("topic1").hash(); - let (id, m) = gen_testm(10, topic1_hash); - - mc.put(&id, m); - - // Try to get an incorrect ID - let wrong_id = MessageId::new(b"wrongid"); - let fetched = mc.get(&wrong_id); - assert!(fetched.is_none()); - } - - #[test] - /// Test attempting to 'get' empty message cache. - fn test_get_empty() { - let mc = new_cache(10, 15); - - // Try to get an incorrect ID - let wrong_string = MessageId::new(b"imempty"); - let fetched = mc.get(&wrong_string); - assert!(fetched.is_none()); - } - - #[test] - /// Test shift mechanism. - fn test_shift() { - let mut mc = new_cache(1, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - // Make sure no messages deleted - assert!(mc.msgs.len() == 10); - } - - #[test] - /// Test Shift with no additions. - fn test_empty_shift() { - let mut mc = new_cache(1, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - mc.shift(); - - assert!(mc.history[2].len() == 10); - assert!(mc.history[1].is_empty()); - assert!(mc.history[0].is_empty()); - } - - #[test] - /// Test shift to see if the last history messages are removed. - fn test_remove_last_from_shift() { - let mut mc = new_cache(4, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - // Shift right until deleting messages - mc.shift(); - mc.shift(); - mc.shift(); - mc.shift(); - - assert_eq!(mc.history[mc.history.len() - 1].len(), 10); - - // Shift and delete the messages - mc.shift(); - assert_eq!(mc.history[mc.history.len() - 1].len(), 0); - assert_eq!(mc.history[0].len(), 0); - assert_eq!(mc.msgs.len(), 0); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/metrics.rs b/beacon_node/lighthouse_network/gossipsub/src/metrics.rs deleted file mode 100644 index 2989f95a26..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/metrics.rs +++ /dev/null @@ -1,800 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! A set of metrics used to help track and diagnose the network behaviour of the gossipsub -//! protocol. - -use std::collections::HashMap; - -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::{Family, MetricConstructor}; -use prometheus_client::metrics::gauge::Gauge; -use prometheus_client::metrics::histogram::{linear_buckets, Histogram}; -use prometheus_client::registry::Registry; - -use super::topic::TopicHash; -use super::types::{MessageAcceptance, PeerKind}; - -// Default value that limits for how many topics do we store metrics. -const DEFAULT_MAX_TOPICS: usize = 300; - -// Default value that limits how many topics for which there has never been a subscription do we -// store metrics. -const DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS: usize = 100; - -#[derive(Debug, Clone)] -pub struct Config { - /// This provides an upper bound to the number of mesh topics we create metrics for. It - /// prevents unbounded labels being created in the metrics. - pub max_topics: usize, - /// Mesh topics are controlled by the user via subscriptions whereas non-mesh topics are - /// determined by users on the network. This limit permits a fixed amount of topics to allow, - /// in-addition to the mesh topics. - pub max_never_subscribed_topics: usize, - /// Buckets used for the score histograms. - pub score_buckets: Vec, -} - -impl Config { - /// Create buckets for the score histograms based on score thresholds. - pub fn buckets_using_scoring_thresholds(&mut self, params: &super::PeerScoreThresholds) { - self.score_buckets = vec![ - params.graylist_threshold, - params.publish_threshold, - params.gossip_threshold, - params.gossip_threshold / 2.0, - params.gossip_threshold / 4.0, - 0.0, - 1.0, - 10.0, - 100.0, - ]; - } -} - -impl Default for Config { - fn default() -> Self { - // Some sensible defaults - let gossip_threshold = -4000.0; - let publish_threshold = -8000.0; - let graylist_threshold = -16000.0; - let score_buckets: Vec = vec![ - graylist_threshold, - publish_threshold, - gossip_threshold, - gossip_threshold / 2.0, - gossip_threshold / 4.0, - 0.0, - 1.0, - 10.0, - 100.0, - ]; - Config { - max_topics: DEFAULT_MAX_TOPICS, - max_never_subscribed_topics: DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS, - score_buckets, - } - } -} - -/// Whether we have ever been subscribed to this topic. -type EverSubscribed = bool; - -/// A collection of metrics used throughout the Gossipsub behaviour. -pub(crate) struct Metrics { - /* Configuration parameters */ - /// Maximum number of topics for which we store metrics. This helps keep the metrics bounded. - max_topics: usize, - /// Maximum number of topics for which we store metrics, where the topic in not one to which we - /// have subscribed at some point. This helps keep the metrics bounded, since these topics come - /// from received messages and not explicit application subscriptions. - max_never_subscribed_topics: usize, - - /* Auxiliary variables */ - /// Information needed to decide if a topic is allowed or not. - topic_info: HashMap, - - /* Metrics per known topic */ - /// Status of our subscription to this topic. This metric allows analyzing other topic metrics - /// filtered by our current subscription status. - topic_subscription_status: Family, - /// Number of peers subscribed to each topic. This allows us to analyze a topic's behaviour - /// regardless of our subscription status. - topic_peers_count: Family, - /// The number of invalid messages received for a given topic. - invalid_messages: Family, - /// The number of messages accepted by the application (validation result). - accepted_messages: Family, - /// The number of messages ignored by the application (validation result). - ignored_messages: Family, - /// The number of messages rejected by the application (validation result). - rejected_messages: Family, - /// The number of publish messages dropped by the sender. - publish_messages_dropped: Family, - /// The number of forward messages dropped by the sender. - forward_messages_dropped: Family, - - /* Metrics regarding mesh state */ - /// Number of peers in our mesh. This metric should be updated with the count of peers for a - /// topic in the mesh regardless of inclusion and churn events. - mesh_peer_counts: Family, - /// Number of times we include peers in a topic mesh for different reasons. - mesh_peer_inclusion_events: Family, - /// Number of times we remove peers in a topic mesh for different reasons. - mesh_peer_churn_events: Family, - - /* Metrics regarding messages sent/received */ - /// Number of gossip messages sent to each topic. - topic_msg_sent_counts: Family, - /// Bytes from gossip messages sent to each topic. - topic_msg_sent_bytes: Family, - /// Number of gossipsub messages published to each topic. - topic_msg_published: Family, - - /// Number of gossipsub messages received on each topic (without filtering duplicates). - topic_msg_recv_counts_unfiltered: Family, - /// Number of gossipsub messages received on each topic (after filtering duplicates). - topic_msg_recv_counts: Family, - /// Bytes received from gossip messages for each topic. - topic_msg_recv_bytes: Family, - - /* Metrics related to scoring */ - /// Histogram of the scores for each mesh topic. - score_per_mesh: Family, - /// A counter of the kind of penalties being applied to peers. - scoring_penalties: Family, - - /* General Metrics */ - /// Gossipsub supports floodsub, gossipsub v1.0 and gossipsub v1.1. Peers are classified based - /// on which protocol they support. This metric keeps track of the number of peers that are - /// connected of each type. - peers_per_protocol: Family, - /// The time it takes to complete one iteration of the heartbeat. - heartbeat_duration: Histogram, - - /* Performance metrics */ - /// When the user validates a message, it tries to re propagate it to its mesh peers. If the - /// message expires from the memcache before it can be validated, we count this a cache miss - /// and it is an indicator that the memcache size should be increased. - memcache_misses: Counter, - /// The number of times we have decided that an IWANT control message is required for this - /// topic. A very high metric might indicate an underperforming network. - topic_iwant_msgs: Family, - - /// The number of times we have received an IDONTWANT control message. - idontwant_msgs: Counter, - - /// The number of msg_id's we have received in every IDONTWANT control message. - idontwant_msgs_ids: Counter, - - /// The number of bytes we have received in every IDONTWANT control message. - idontwant_bytes: Counter, - - /// Number of IDONTWANT messages sent per topic. - idontwant_messages_sent_per_topic: Family, - - /// Number of full messages we received that we previously sent a IDONTWANT for. - idontwant_messages_ignored_per_topic: Family, - - /// Count of duplicate messages we have received from mesh peers for a given topic. - mesh_duplicates: Family, - - /// Count of duplicate messages we have received from by requesting them over iwant for a given topic. - iwant_duplicates: Family, - - /// The size of the priority queue. - priority_queue_size: Histogram, - /// The size of the non-priority queue. - non_priority_queue_size: Histogram, -} - -impl Metrics { - pub(crate) fn new(registry: &mut Registry, config: Config) -> Self { - // Destructure the config to be sure everything is used. - let Config { - max_topics, - max_never_subscribed_topics, - score_buckets, - } = config; - - macro_rules! register_family { - ($name:expr, $help:expr) => {{ - let fam = Family::default(); - registry.register($name, $help, fam.clone()); - fam - }}; - } - - let topic_subscription_status = register_family!( - "topic_subscription_status", - "Subscription status per known topic" - ); - let topic_peers_count = register_family!( - "topic_peers_counts", - "Number of peers subscribed to each topic" - ); - - let invalid_messages = register_family!( - "invalid_messages_per_topic", - "Number of invalid messages received for each topic" - ); - - let accepted_messages = register_family!( - "accepted_messages_per_topic", - "Number of accepted messages received for each topic" - ); - - let ignored_messages = register_family!( - "ignored_messages_per_topic", - "Number of ignored messages received for each topic" - ); - - let rejected_messages = register_family!( - "rejected_messages_per_topic", - "Number of rejected messages received for each topic" - ); - - let publish_messages_dropped = register_family!( - "publish_messages_dropped_per_topic", - "Number of publish messages dropped per topic" - ); - - let forward_messages_dropped = register_family!( - "forward_messages_dropped_per_topic", - "Number of forward messages dropped per topic" - ); - - let mesh_peer_counts = register_family!( - "mesh_peer_counts", - "Number of peers in each topic in our mesh" - ); - let mesh_peer_inclusion_events = register_family!( - "mesh_peer_inclusion_events", - "Number of times a peer gets added to our mesh for different reasons" - ); - let mesh_peer_churn_events = register_family!( - "mesh_peer_churn_events", - "Number of times a peer gets removed from our mesh for different reasons" - ); - let topic_msg_sent_counts = register_family!( - "topic_msg_sent_counts", - "Number of gossip messages sent to each topic" - ); - let topic_msg_published = register_family!( - "topic_msg_published", - "Number of gossip messages published to each topic" - ); - let topic_msg_sent_bytes = register_family!( - "topic_msg_sent_bytes", - "Bytes from gossip messages sent to each topic" - ); - - let topic_msg_recv_counts_unfiltered = register_family!( - "topic_msg_recv_counts_unfiltered", - "Number of gossip messages received on each topic (without duplicates being filtered)" - ); - - let topic_msg_recv_counts = register_family!( - "topic_msg_recv_counts", - "Number of gossip messages received on each topic (after duplicates have been filtered)" - ); - let topic_msg_recv_bytes = register_family!( - "topic_msg_recv_bytes", - "Bytes received from gossip messages for each topic" - ); - - let hist_builder = HistBuilder { - buckets: score_buckets, - }; - - let score_per_mesh: Family<_, _, HistBuilder> = Family::new_with_constructor(hist_builder); - registry.register( - "score_per_mesh", - "Histogram of scores per mesh topic", - score_per_mesh.clone(), - ); - - let scoring_penalties = register_family!( - "scoring_penalties", - "Counter of types of scoring penalties given to peers" - ); - let peers_per_protocol = register_family!( - "peers_per_protocol", - "Number of connected peers by protocol type" - ); - - let heartbeat_duration = Histogram::new(linear_buckets(0.0, 50.0, 10)); - registry.register( - "heartbeat_duration", - "Histogram of observed heartbeat durations", - heartbeat_duration.clone(), - ); - - let topic_iwant_msgs = register_family!( - "topic_iwant_msgs", - "Number of times we have decided an IWANT is required for this topic" - ); - - let idontwant_msgs = { - let metric = Counter::default(); - registry.register( - "idontwant_msgs", - "The number of times we have received an IDONTWANT control message", - metric.clone(), - ); - metric - }; - - let idontwant_msgs_ids = { - let metric = Counter::default(); - registry.register( - "idontwant_msgs_ids", - "The number of msg_id's we have received in every IDONTWANT control message.", - metric.clone(), - ); - metric - }; - - // IDONTWANT messages sent per topic - let idontwant_messages_sent_per_topic = register_family!( - "idonttwant_messages_sent_per_topic", - "Number of IDONTWANT messages sent per topic" - ); - - // IDONTWANTs which were ignored, and we still received the message per topic - let idontwant_messages_ignored_per_topic = register_family!( - "idontwant_messages_ignored_per_topic", - "IDONTWANT messages that were sent but we received the full message regardless" - ); - - let mesh_duplicates = register_family!( - "mesh_duplicates_per_topic", - "Count of duplicate messages received from mesh peers per topic" - ); - - let iwant_duplicates = register_family!( - "iwant_duplicates_per_topic", - "Count of duplicate messages received from non-mesh peers that we sent iwants for" - ); - - let idontwant_bytes = { - let metric = Counter::default(); - registry.register( - "idontwant_bytes", - "The total bytes we have received an IDONTWANT control messages", - metric.clone(), - ); - metric - }; - - let memcache_misses = { - let metric = Counter::default(); - registry.register( - "memcache_misses", - "Number of times a message is not found in the duplicate cache when validating", - metric.clone(), - ); - metric - }; - - let priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); - registry.register( - "priority_queue_size", - "Histogram of observed priority queue sizes", - priority_queue_size.clone(), - ); - - let non_priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); - registry.register( - "non_priority_queue_size", - "Histogram of observed non-priority queue sizes", - non_priority_queue_size.clone(), - ); - - Self { - max_topics, - max_never_subscribed_topics, - topic_info: HashMap::default(), - topic_subscription_status, - topic_peers_count, - invalid_messages, - accepted_messages, - ignored_messages, - rejected_messages, - publish_messages_dropped, - forward_messages_dropped, - mesh_peer_counts, - mesh_peer_inclusion_events, - mesh_peer_churn_events, - topic_msg_sent_counts, - topic_msg_sent_bytes, - topic_msg_published, - topic_msg_recv_counts_unfiltered, - topic_msg_recv_counts, - topic_msg_recv_bytes, - score_per_mesh, - scoring_penalties, - peers_per_protocol, - heartbeat_duration, - memcache_misses, - topic_iwant_msgs, - idontwant_msgs, - idontwant_bytes, - idontwant_msgs_ids, - idontwant_messages_sent_per_topic, - idontwant_messages_ignored_per_topic, - mesh_duplicates, - iwant_duplicates, - priority_queue_size, - non_priority_queue_size, - } - } - - fn non_subscription_topics_count(&self) -> usize { - self.topic_info - .values() - .filter(|&ever_subscribed| !ever_subscribed) - .count() - } - - /// Registers a topic if not already known and if the bounds allow it. - fn register_topic(&mut self, topic: &TopicHash) -> Result<(), ()> { - if self.topic_info.contains_key(topic) { - Ok(()) - } else if self.topic_info.len() < self.max_topics - && self.non_subscription_topics_count() < self.max_never_subscribed_topics - { - // This is a topic without an explicit subscription and we register it if we are within - // the configured bounds. - self.topic_info.entry(topic.clone()).or_insert(false); - self.topic_subscription_status.get_or_create(topic).set(0); - Ok(()) - } else { - // We don't know this topic and there is no space left to store it - Err(()) - } - } - - /// Registers a set of topics that we want to store calculate metrics for. - pub(crate) fn register_allowed_topics(&mut self, topics: Vec) { - for topic_hash in topics { - self.topic_info.insert(topic_hash, true); - } - } - - /// Increase the number of peers that are subscribed to this topic. - pub(crate) fn inc_topic_peers(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_peers_count.get_or_create(topic).inc(); - } - } - - /// Decrease the number of peers that are subscribed to this topic. - pub(crate) fn dec_topic_peers(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_peers_count.get_or_create(topic).dec(); - } - } - - /* Mesh related methods */ - - /// Registers the subscription to a topic if the configured limits allow it. - /// Sets the registered number of peers in the mesh to 0. - pub(crate) fn joined(&mut self, topic: &TopicHash) { - if self.topic_info.contains_key(topic) || self.topic_info.len() < self.max_topics { - self.topic_info.insert(topic.clone(), true); - let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(1); - debug_assert_eq!(was_subscribed, 0); - self.mesh_peer_counts.get_or_create(topic).set(0); - } - } - - /// Registers the unsubscription to a topic if the topic was previously allowed. - /// Sets the registered number of peers in the mesh to 0. - pub(crate) fn left(&mut self, topic: &TopicHash) { - if self.topic_info.contains_key(topic) { - // Depending on the configured topic bounds we could miss a mesh topic. - // So, check first if the topic was previously allowed. - let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(0); - debug_assert_eq!(was_subscribed, 1); - self.mesh_peer_counts.get_or_create(topic).set(0); - } - } - - /// Register the inclusion of peers in our mesh due to some reason. - pub(crate) fn peers_included(&mut self, topic: &TopicHash, reason: Inclusion, count: usize) { - if self.register_topic(topic).is_ok() { - self.mesh_peer_inclusion_events - .get_or_create(&InclusionLabel { - hash: topic.to_string(), - reason, - }) - .inc_by(count as u64); - } - } - - /// Register the removal of peers in our mesh due to some reason. - pub(crate) fn peers_removed(&mut self, topic: &TopicHash, reason: Churn, count: usize) { - if self.register_topic(topic).is_ok() { - self.mesh_peer_churn_events - .get_or_create(&ChurnLabel { - hash: topic.to_string(), - reason, - }) - .inc_by(count as u64); - } - } - - /// Register the current number of peers in our mesh for this topic. - pub(crate) fn set_mesh_peers(&mut self, topic: &TopicHash, count: usize) { - if self.register_topic(topic).is_ok() { - // Due to limits, this topic could have not been allowed, so we check. - self.mesh_peer_counts.get_or_create(topic).set(count as i64); - } - } - - /// Register that an invalid message was received on a specific topic. - pub(crate) fn register_invalid_message(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.invalid_messages.get_or_create(topic).inc(); - } - } - - /// Register a score penalty. - pub(crate) fn register_score_penalty(&mut self, penalty: Penalty) { - self.scoring_penalties - .get_or_create(&PenaltyLabel { penalty }) - .inc(); - } - - /// Registers that a message was published on a specific topic. - pub(crate) fn register_published_message(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_msg_published.get_or_create(topic).inc(); - } - } - - /// Register sending a message over a topic. - pub(crate) fn msg_sent(&mut self, topic: &TopicHash, bytes: usize) { - if self.register_topic(topic).is_ok() { - self.topic_msg_sent_counts.get_or_create(topic).inc(); - self.topic_msg_sent_bytes - .get_or_create(topic) - .inc_by(bytes as u64); - } - } - - /// Register sending a message over a topic. - pub(crate) fn publish_msg_dropped(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.publish_messages_dropped.get_or_create(topic).inc(); - } - } - - /// Register dropping a message over a topic. - pub(crate) fn forward_msg_dropped(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.forward_messages_dropped.get_or_create(topic).inc(); - } - } - - /// Register that a message was received (and was not a duplicate). - pub(crate) fn msg_recvd(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_msg_recv_counts.get_or_create(topic).inc(); - } - } - - /// Register that a message was received (could have been a duplicate). - pub(crate) fn msg_recvd_unfiltered(&mut self, topic: &TopicHash, bytes: usize) { - if self.register_topic(topic).is_ok() { - self.topic_msg_recv_counts_unfiltered - .get_or_create(topic) - .inc(); - self.topic_msg_recv_bytes - .get_or_create(topic) - .inc_by(bytes as u64); - } - } - - /// Register a duplicate message received from a mesh peer. - pub(crate) fn mesh_duplicates(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.mesh_duplicates.get_or_create(topic).inc(); - } - } - - /// Register a duplicate message received from a non-mesh peer on an iwant request. - pub(crate) fn iwant_duplicates(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.iwant_duplicates.get_or_create(topic).inc(); - } - } - - pub(crate) fn register_msg_validation( - &mut self, - topic: &TopicHash, - validation: &MessageAcceptance, - ) { - if self.register_topic(topic).is_ok() { - match validation { - MessageAcceptance::Accept => self.accepted_messages.get_or_create(topic).inc(), - MessageAcceptance::Ignore => self.ignored_messages.get_or_create(topic).inc(), - MessageAcceptance::Reject => self.rejected_messages.get_or_create(topic).inc(), - }; - } - } - - /// Register a memcache miss. - pub(crate) fn memcache_miss(&mut self) { - self.memcache_misses.inc(); - } - - /// Register sending an IWANT msg for this topic. - pub(crate) fn register_iwant(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_iwant_msgs.get_or_create(topic).inc(); - } - } - - /// Register receiving the total bytes of an IDONTWANT control message. - pub(crate) fn register_idontwant_bytes(&mut self, bytes: usize) { - self.idontwant_bytes.inc_by(bytes as u64); - } - - /// Register receiving an IDONTWANT control message for a given topic. - pub(crate) fn register_idontwant_messages_sent_per_topic(&mut self, topic: &TopicHash) { - self.idontwant_messages_sent_per_topic - .get_or_create(topic) - .inc(); - } - - /// Register receiving a message for an already sent IDONTWANT. - pub(crate) fn register_idontwant_messages_ignored_per_topic(&mut self, topic: &TopicHash) { - self.idontwant_messages_ignored_per_topic - .get_or_create(topic) - .inc(); - } - - /// Register receiving an IDONTWANT msg for this topic. - pub(crate) fn register_idontwant(&mut self, msgs: usize) { - self.idontwant_msgs.inc(); - self.idontwant_msgs_ids.inc_by(msgs as u64); - } - - /// Observes a heartbeat duration. - pub(crate) fn observe_heartbeat_duration(&mut self, millis: u64) { - self.heartbeat_duration.observe(millis as f64); - } - - /// Observes a priority queue size. - pub(crate) fn observe_priority_queue_size(&mut self, len: usize) { - self.priority_queue_size.observe(len as f64); - } - - /// Observes a non-priority queue size. - pub(crate) fn observe_non_priority_queue_size(&mut self, len: usize) { - self.non_priority_queue_size.observe(len as f64); - } - - /// Observe a score of a mesh peer. - pub(crate) fn observe_mesh_peers_score(&mut self, topic: &TopicHash, score: f64) { - if self.register_topic(topic).is_ok() { - self.score_per_mesh.get_or_create(topic).observe(score); - } - } - - /// Register a new peers connection based on its protocol. - pub(crate) fn peer_protocol_connected(&mut self, kind: PeerKind) { - self.peers_per_protocol - .get_or_create(&ProtocolLabel { protocol: kind }) - .inc(); - } - - /// Removes a peer from the counter based on its protocol when it disconnects. - pub(crate) fn peer_protocol_disconnected(&mut self, kind: PeerKind) { - let metric = self - .peers_per_protocol - .get_or_create(&ProtocolLabel { protocol: kind }); - if metric.get() != 0 { - // decrement the counter - metric.set(metric.get() - 1); - } - } -} - -/// Reasons why a peer was included in the mesh. -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Inclusion { - /// Peer was a fanaout peer. - Fanout, - /// Included from random selection. - Random, - /// Peer subscribed. - Subscribed, - /// Peer was included to fill the outbound quota. - Outbound, -} - -/// Reasons why a peer was removed from the mesh. -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Churn { - /// Peer disconnected. - Dc, - /// Peer had a bad score. - BadScore, - /// Peer sent a PRUNE. - Prune, - /// Peer unsubscribed. - Unsub, - /// Too many peers. - Excess, -} - -/// Kinds of reasons a peer's score has been penalized -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Penalty { - /// A peer grafted before waiting the back-off time. - GraftBackoff, - /// A Peer did not respond to an IWANT request in time. - BrokenPromise, - /// A Peer did not send enough messages as expected. - MessageDeficit, - /// Too many peers under one IP address. - IPColocation, -} - -/// Label for the mesh inclusion event metrics. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct InclusionLabel { - hash: String, - reason: Inclusion, -} - -/// Label for the mesh churn event metrics. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct ChurnLabel { - hash: String, - reason: Churn, -} - -/// Label for the kinds of protocols peers can connect as. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct ProtocolLabel { - protocol: PeerKind, -} - -/// Label for the kinds of scoring penalties that can occur -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct PenaltyLabel { - penalty: Penalty, -} - -#[derive(Clone)] -struct HistBuilder { - buckets: Vec, -} - -impl MetricConstructor for HistBuilder { - fn new_metric(&self) -> Histogram { - Histogram::new(self.buckets.clone().into_iter()) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/mod.rs deleted file mode 100644 index 8ccdc32cdd..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/mod.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Implementation of the [Gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md) protocol. -//! -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! floodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! () provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence numbers in -//! this implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. When messages are signed, they are monotonically increasing integers starting from a -//! random value and wrapping around u64::MAX. When messages are unsigned, they are chosen at random. -//! NOTE: These numbers are sequential in the current go implementation. -//! -//! # Peer Discovery -//! -//! Gossipsub does not provide peer discovery by itself. Peer discovery is the process by which -//! peers in a p2p network exchange information about each other among other reasons to become resistant -//! against the failure or replacement of the -//! [boot nodes](https://docs.libp2p.io/reference/glossary/#boot-node) of the network. -//! -//! Peer -//! discovery can e.g. be implemented with the help of the [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) protocol -//! in combination with the [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol. See the -//! Kademlia implementation documentation for more information. -//! -//! # Using Gossipsub -//! -//! ## Gossipsub Config -//! -//! The [`Config`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`Config`]: struct.Config.html -//! -//! This struct implements the [`Default`] trait and can be initialised via -//! [`Config::default()`]. -//! -//! -//! ## Behaviour -//! -//! The [`Behaviour`] struct implements the [`libp2p_swarm::NetworkBehaviour`] trait allowing it to -//! act as the routing behaviour in a [`libp2p_swarm::Swarm`]. This struct requires an instance of -//! [`PeerId`](libp2p_identity::PeerId) and [`Config`]. -//! -//! [`Behaviour`]: struct.Behaviour.html - -//! ## Example -//! -//! For an example on how to use gossipsub, see the [chat-example](https://github.com/libp2p/rust-libp2p/tree/master/examples/chat). - -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -mod backoff; -mod behaviour; -mod config; -mod error; -mod gossip_promises; -mod handler; -mod mcache; -mod metrics; -mod peer_score; -mod protocol; -mod rpc_proto; -mod subscription_filter; -mod time_cache; -mod topic; -mod transform; -mod types; - -pub use self::behaviour::{Behaviour, Event, MessageAuthenticity}; -pub use self::config::{Config, ConfigBuilder, ValidationMode, Version}; -pub use self::error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}; -pub use self::metrics::Config as MetricsConfig; -pub use self::peer_score::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; -pub use self::subscription_filter::{ - AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, - MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, - WhitelistSubscriptionFilter, -}; -pub use self::topic::{Hasher, Topic, TopicHash}; -pub use self::transform::{DataTransform, IdentityTransform}; -pub use self::types::{Message, MessageAcceptance, MessageId, RawMessage}; -pub type IdentTopic = Topic; -pub type Sha256Topic = Topic; -pub use self::types::FailedMessages; diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs deleted file mode 100644 index ec6fe7bdb6..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs +++ /dev/null @@ -1,937 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! -//! Manages and stores the Scoring logic of a particular peer on the gossipsub behaviour. - -use super::metrics::{Metrics, Penalty}; -use super::time_cache::TimeCache; -use super::{MessageId, TopicHash}; -use libp2p::identity::PeerId; -use std::collections::{hash_map, HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; -use web_time::Instant; - -mod params; -use super::ValidationError; -pub use params::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; - -#[cfg(test)] -mod tests; - -/// The number of seconds delivery messages are stored in the cache. -const TIME_CACHE_DURATION: u64 = 120; - -pub(crate) struct PeerScore { - pub(crate) params: PeerScoreParams, - /// The score parameters. - peer_stats: HashMap, - /// Tracking peers per IP. - peer_ips: HashMap>, - /// Message delivery tracking. This is a time-cache of [`DeliveryRecord`]s. - deliveries: TimeCache, - /// callback for monitoring message delivery times - message_delivery_time_callback: Option, -} - -/// General statistics for a given gossipsub peer. -struct PeerStats { - /// Connection status of the peer. - status: ConnectionStatus, - /// Stats per topic. - topics: HashMap, - /// IP tracking for individual peers. - known_ips: HashSet, - /// Behaviour penalty that is applied to the peer, assigned by the behaviour. - behaviour_penalty: f64, - /// Application specific score. Can be manipulated by calling PeerScore::set_application_score - application_score: f64, - /// Scoring based on how whether this peer consumes messages fast enough or not. - slow_peer_penalty: f64, -} - -enum ConnectionStatus { - /// The peer is connected. - Connected, - /// The peer is disconnected - Disconnected { - /// Expiration time of the score state for disconnected peers. - expire: Instant, - }, -} - -impl Default for PeerStats { - fn default() -> Self { - PeerStats { - status: ConnectionStatus::Connected, - topics: HashMap::new(), - known_ips: HashSet::new(), - behaviour_penalty: 0f64, - application_score: 0f64, - slow_peer_penalty: 0f64, - } - } -} - -impl PeerStats { - /// Returns a mutable reference to topic stats if they exist, otherwise if the supplied parameters score the - /// topic, inserts the default stats and returns a reference to those. If neither apply, returns None. - pub(crate) fn stats_or_default_mut( - &mut self, - topic_hash: TopicHash, - params: &PeerScoreParams, - ) -> Option<&mut TopicStats> { - if params.topics.contains_key(&topic_hash) { - Some(self.topics.entry(topic_hash).or_default()) - } else { - self.topics.get_mut(&topic_hash) - } - } -} - -/// Stats assigned to peer for each topic. -struct TopicStats { - mesh_status: MeshStatus, - /// Number of first message deliveries. - first_message_deliveries: f64, - /// True if the peer has been in the mesh for enough time to activate mesh message deliveries. - mesh_message_deliveries_active: bool, - /// Number of message deliveries from the mesh. - mesh_message_deliveries: f64, - /// Mesh rate failure penalty. - mesh_failure_penalty: f64, - /// Invalid message counter. - invalid_message_deliveries: f64, -} - -impl TopicStats { - /// Returns true if the peer is in the `mesh`. - pub(crate) fn in_mesh(&self) -> bool { - matches!(self.mesh_status, MeshStatus::Active { .. }) - } -} - -/// Status defining a peer's inclusion in the mesh and associated parameters. -enum MeshStatus { - Active { - /// The time the peer was last GRAFTed; - graft_time: Instant, - /// The time the peer has been in the mesh. - mesh_time: Duration, - }, - InActive, -} - -impl MeshStatus { - /// Initialises a new [`MeshStatus::Active`] mesh status. - pub(crate) fn new_active() -> Self { - MeshStatus::Active { - graft_time: Instant::now(), - mesh_time: Duration::from_secs(0), - } - } -} - -impl Default for TopicStats { - fn default() -> Self { - TopicStats { - mesh_status: MeshStatus::InActive, - first_message_deliveries: Default::default(), - mesh_message_deliveries_active: Default::default(), - mesh_message_deliveries: Default::default(), - mesh_failure_penalty: Default::default(), - invalid_message_deliveries: Default::default(), - } - } -} - -#[derive(PartialEq, Debug)] -struct DeliveryRecord { - status: DeliveryStatus, - first_seen: Instant, - peers: HashSet, -} - -#[derive(PartialEq, Debug)] -enum DeliveryStatus { - /// Don't know (yet) if the message is valid. - Unknown, - /// The message is valid together with the validated time. - Valid(Instant), - /// The message is invalid. - Invalid, - /// Instructed by the validator to ignore the message. - Ignored, -} - -impl Default for DeliveryRecord { - fn default() -> Self { - DeliveryRecord { - status: DeliveryStatus::Unknown, - first_seen: Instant::now(), - peers: HashSet::new(), - } - } -} - -impl PeerScore { - /// Creates a new [`PeerScore`] using a given set of peer scoring parameters. - #[allow(dead_code)] - pub(crate) fn new(params: PeerScoreParams) -> Self { - Self::new_with_message_delivery_time_callback(params, None) - } - - pub(crate) fn new_with_message_delivery_time_callback( - params: PeerScoreParams, - callback: Option, - ) -> Self { - PeerScore { - params, - peer_stats: HashMap::new(), - peer_ips: HashMap::new(), - deliveries: TimeCache::new(Duration::from_secs(TIME_CACHE_DURATION)), - message_delivery_time_callback: callback, - } - } - - /// Returns the score for a peer - pub(crate) fn score(&self, peer_id: &PeerId) -> f64 { - self.metric_score(peer_id, None) - } - - /// Returns the score for a peer, logging metrics. This is called from the heartbeat and - /// increments the metric counts for penalties. - pub(crate) fn metric_score(&self, peer_id: &PeerId, mut metrics: Option<&mut Metrics>) -> f64 { - let Some(peer_stats) = self.peer_stats.get(peer_id) else { - return 0.0; - }; - let mut score = 0.0; - - // topic scores - for (topic, topic_stats) in peer_stats.topics.iter() { - // topic parameters - if let Some(topic_params) = self.params.topics.get(topic) { - // we are tracking the topic - - // the topic score - let mut topic_score = 0.0; - - // P1: time in mesh - if let MeshStatus::Active { mesh_time, .. } = topic_stats.mesh_status { - let p1 = { - let v = mesh_time.as_secs_f64() - / topic_params.time_in_mesh_quantum.as_secs_f64(); - if v < topic_params.time_in_mesh_cap { - v - } else { - topic_params.time_in_mesh_cap - } - }; - topic_score += p1 * topic_params.time_in_mesh_weight; - } - - // P2: first message deliveries - let p2 = { - let v = topic_stats.first_message_deliveries; - if v < topic_params.first_message_deliveries_cap { - v - } else { - topic_params.first_message_deliveries_cap - } - }; - topic_score += p2 * topic_params.first_message_deliveries_weight; - - // P3: mesh message deliveries - if topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries - < topic_params.mesh_message_deliveries_threshold - { - let deficit = topic_params.mesh_message_deliveries_threshold - - topic_stats.mesh_message_deliveries; - let p3 = deficit * deficit; - topic_score += p3 * topic_params.mesh_message_deliveries_weight; - if let Some(metrics) = metrics.as_mut() { - metrics.register_score_penalty(Penalty::MessageDeficit); - } - tracing::debug!( - peer=%peer_id, - %topic, - %deficit, - penalty=%topic_score, - "[Penalty] The peer has a mesh deliveries deficit and will be penalized" - ); - } - - // P3b: - // NOTE: the weight of P3b is negative (validated in TopicScoreParams.validate), so this detracts. - let p3b = topic_stats.mesh_failure_penalty; - topic_score += p3b * topic_params.mesh_failure_penalty_weight; - - // P4: invalid messages - // NOTE: the weight of P4 is negative (validated in TopicScoreParams.validate), so this detracts. - let p4 = - topic_stats.invalid_message_deliveries * topic_stats.invalid_message_deliveries; - topic_score += p4 * topic_params.invalid_message_deliveries_weight; - - // update score, mixing with topic weight - score += topic_score * topic_params.topic_weight; - } - } - - // apply the topic score cap, if any - if self.params.topic_score_cap > 0f64 && score > self.params.topic_score_cap { - score = self.params.topic_score_cap; - } - - // P5: application-specific score - let p5 = peer_stats.application_score; - score += p5 * self.params.app_specific_weight; - - // P6: IP collocation factor - for ip in peer_stats.known_ips.iter() { - if self.params.ip_colocation_factor_whitelist.contains(ip) { - continue; - } - - // P6 has a cliff (ip_colocation_factor_threshold); it's only applied iff - // at least that many peers are connected to us from that source IP - // addr. It is quadratic, and the weight is negative (validated by - // peer_score_params.validate()). - if let Some(peers_in_ip) = self.peer_ips.get(ip).map(|peers| peers.len()) { - if (peers_in_ip as f64) > self.params.ip_colocation_factor_threshold { - let surplus = (peers_in_ip as f64) - self.params.ip_colocation_factor_threshold; - let p6 = surplus * surplus; - if let Some(metrics) = metrics.as_mut() { - metrics.register_score_penalty(Penalty::IPColocation); - } - tracing::debug!( - peer=%peer_id, - surplus_ip=%ip, - surplus=%surplus, - "[Penalty] The peer gets penalized because of too many peers with the same ip" - ); - score += p6 * self.params.ip_colocation_factor_weight; - } - } - } - - // P7: behavioural pattern penalty - if peer_stats.behaviour_penalty > self.params.behaviour_penalty_threshold { - let excess = peer_stats.behaviour_penalty - self.params.behaviour_penalty_threshold; - let p7 = excess * excess; - score += p7 * self.params.behaviour_penalty_weight; - } - - // Slow peer weighting - if peer_stats.slow_peer_penalty > self.params.slow_peer_threshold { - let excess = peer_stats.slow_peer_penalty - self.params.slow_peer_threshold; - score += excess * self.params.slow_peer_weight; - } - - score - } - - pub(crate) fn add_penalty(&mut self, peer_id: &PeerId, count: usize) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - tracing::debug!( - peer=%peer_id, - %count, - "[Penalty] Behavioral penalty for peer" - ); - peer_stats.behaviour_penalty += count as f64; - } - } - - fn remove_ips_for_peer( - peer_stats: &PeerStats, - peer_ips: &mut HashMap>, - peer_id: &PeerId, - ) { - for ip in peer_stats.known_ips.iter() { - if let Some(peer_set) = peer_ips.get_mut(ip) { - peer_set.remove(peer_id); - } - } - } - - pub(crate) fn refresh_scores(&mut self) { - let now = Instant::now(); - let params_ref = &self.params; - let peer_ips_ref = &mut self.peer_ips; - self.peer_stats.retain(|peer_id, peer_stats| { - if let ConnectionStatus::Disconnected { expire } = peer_stats.status { - // has the retention period expired? - if now > expire { - // yes, throw it away (but clean up the IP tracking first) - Self::remove_ips_for_peer(peer_stats, peer_ips_ref, peer_id); - // re address this, use retain or entry - return false; - } - - // we don't decay retained scores, as the peer is not active. - // this way the peer cannot reset a negative score by simply disconnecting and reconnecting, - // unless the retention period has elapsed. - // similarly, a well behaved peer does not lose its score by getting disconnected. - return true; - } - - for (topic, topic_stats) in peer_stats.topics.iter_mut() { - // the topic parameters - if let Some(topic_params) = params_ref.topics.get(topic) { - // decay counters - topic_stats.first_message_deliveries *= - topic_params.first_message_deliveries_decay; - if topic_stats.first_message_deliveries < params_ref.decay_to_zero { - topic_stats.first_message_deliveries = 0.0; - } - topic_stats.mesh_message_deliveries *= - topic_params.mesh_message_deliveries_decay; - if topic_stats.mesh_message_deliveries < params_ref.decay_to_zero { - topic_stats.mesh_message_deliveries = 0.0; - } - topic_stats.mesh_failure_penalty *= topic_params.mesh_failure_penalty_decay; - if topic_stats.mesh_failure_penalty < params_ref.decay_to_zero { - topic_stats.mesh_failure_penalty = 0.0; - } - topic_stats.invalid_message_deliveries *= - topic_params.invalid_message_deliveries_decay; - if topic_stats.invalid_message_deliveries < params_ref.decay_to_zero { - topic_stats.invalid_message_deliveries = 0.0; - } - // update mesh time and activate mesh message delivery parameter if need be - if let MeshStatus::Active { - ref mut mesh_time, - ref mut graft_time, - } = topic_stats.mesh_status - { - *mesh_time = now.duration_since(*graft_time); - if *mesh_time > topic_params.mesh_message_deliveries_activation { - topic_stats.mesh_message_deliveries_active = true; - } - } - } - } - - // decay P7 counter - peer_stats.behaviour_penalty *= params_ref.behaviour_penalty_decay; - if peer_stats.behaviour_penalty < params_ref.decay_to_zero { - peer_stats.behaviour_penalty = 0.0; - } - - // decay slow peer score - peer_stats.slow_peer_penalty *= params_ref.slow_peer_decay; - if peer_stats.slow_peer_penalty < params_ref.decay_to_zero { - peer_stats.slow_peer_penalty = 0.0; - } - - true - }); - } - - /// Adds a connected peer to [`PeerScore`], initialising with empty ips (ips get added later - /// through add_ip. - pub(crate) fn add_peer(&mut self, peer_id: PeerId) { - let peer_stats = self.peer_stats.entry(peer_id).or_default(); - - // mark the peer as connected - peer_stats.status = ConnectionStatus::Connected; - } - - /// Adds a new ip to a peer, if the peer is not yet known creates a new peer_stats entry for it - pub(crate) fn add_ip(&mut self, peer_id: &PeerId, ip: IpAddr) { - tracing::trace!(peer=%peer_id, %ip, "Add ip for peer"); - let peer_stats = self.peer_stats.entry(*peer_id).or_default(); - - // Mark the peer as connected (currently the default is connected, but we don't want to - // rely on the default). - peer_stats.status = ConnectionStatus::Connected; - - // Insert the ip - peer_stats.known_ips.insert(ip); - self.peer_ips.entry(ip).or_default().insert(*peer_id); - } - - /// Indicate that a peer has been too slow to consume a message. - pub(crate) fn failed_message_slow_peer(&mut self, peer_id: &PeerId) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.slow_peer_penalty += 1.0; - tracing::debug!(peer=%peer_id, %peer_stats.slow_peer_penalty, "[Penalty] Expired message penalty."); - } - } - - /// Removes an ip from a peer - pub(crate) fn remove_ip(&mut self, peer_id: &PeerId, ip: &IpAddr) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.known_ips.remove(ip); - if let Some(peer_ids) = self.peer_ips.get_mut(ip) { - tracing::trace!(peer=%peer_id, %ip, "Remove ip for peer"); - peer_ids.remove(peer_id); - } else { - tracing::trace!( - peer=%peer_id, - %ip, - "No entry in peer_ips for ip which should get removed for peer" - ); - } - } else { - tracing::trace!( - peer=%peer_id, - %ip, - "No peer_stats for peer which should remove the ip" - ); - } - } - - /// Removes a peer from the score table. This retains peer statistics if their score is - /// non-positive. - pub(crate) fn remove_peer(&mut self, peer_id: &PeerId) { - // we only retain non-positive scores of peers - if self.score(peer_id) > 0f64 { - if let hash_map::Entry::Occupied(entry) = self.peer_stats.entry(*peer_id) { - Self::remove_ips_for_peer(entry.get(), &mut self.peer_ips, peer_id); - entry.remove(); - } - return; - } - - // if the peer is retained (including it's score) the `first_message_delivery` counters - // are reset to 0 and mesh delivery penalties applied. - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - for (topic, topic_stats) in peer_stats.topics.iter_mut() { - topic_stats.first_message_deliveries = 0f64; - - if let Some(threshold) = self - .params - .topics - .get(topic) - .map(|param| param.mesh_message_deliveries_threshold) - { - if topic_stats.in_mesh() - && topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries < threshold - { - let deficit = threshold - topic_stats.mesh_message_deliveries; - topic_stats.mesh_failure_penalty += deficit * deficit; - } - } - - topic_stats.mesh_status = MeshStatus::InActive; - topic_stats.mesh_message_deliveries_active = false; - } - - peer_stats.status = ConnectionStatus::Disconnected { - expire: Instant::now() + self.params.retain_score, - }; - } - } - - /// Handles scoring functionality as a peer GRAFTs to a topic. - pub(crate) fn graft(&mut self, peer_id: &PeerId, topic: impl Into) { - let topic = topic.into(); - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - // if we are scoring the topic, update the mesh status. - if let Some(topic_stats) = peer_stats.stats_or_default_mut(topic, &self.params) { - topic_stats.mesh_status = MeshStatus::new_active(); - topic_stats.mesh_message_deliveries_active = false; - } - } - } - - /// Handles scoring functionality as a peer PRUNEs from a topic. - pub(crate) fn prune(&mut self, peer_id: &PeerId, topic: TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - // if we are scoring the topic, update the mesh status. - if let Some(topic_stats) = peer_stats.stats_or_default_mut(topic.clone(), &self.params) - { - // sticky mesh delivery rate failure penalty - let threshold = self - .params - .topics - .get(&topic) - .expect("Topic must exist in order for there to be topic stats") - .mesh_message_deliveries_threshold; - if topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries < threshold - { - let deficit = threshold - topic_stats.mesh_message_deliveries; - topic_stats.mesh_failure_penalty += deficit * deficit; - } - topic_stats.mesh_message_deliveries_active = false; - topic_stats.mesh_status = MeshStatus::InActive; - } - } - } - - pub(crate) fn validate_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - // adds an empty record with the message id - self.deliveries.entry(msg_id.clone()).or_default(); - - if let Some(callback) = self.message_delivery_time_callback { - if self - .peer_stats - .get(from) - .and_then(|s| s.topics.get(topic_hash)) - .map(|ts| ts.in_mesh()) - .unwrap_or(false) - { - callback(from, topic_hash, 0.0); - } - } - } - - pub(crate) fn deliver_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - self.mark_first_message_delivery(from, topic_hash); - - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - // this should be the first delivery trace - if record.status != DeliveryStatus::Unknown { - tracing::warn!( - peer=%from, - status=?record.status, - first_seen=?record.first_seen.elapsed().as_secs(), - "Unexpected delivery trace" - ); - return; - } - - // mark the message as valid and reward mesh peers that have already forwarded it to us - record.status = DeliveryStatus::Valid(Instant::now()); - for peer in record.peers.iter().cloned().collect::>() { - // this check is to make sure a peer can't send us a message twice and get a double - // count if it is a first delivery - if &peer != from { - self.mark_duplicate_message_delivery(&peer, topic_hash, None); - } - } - } - - /// Similar to `reject_message` except does not require the message id or reason for an invalid message. - pub(crate) fn reject_invalid_message(&mut self, from: &PeerId, topic_hash: &TopicHash) { - tracing::debug!( - peer=%from, - "[Penalty] Message from peer rejected because of ValidationError or SelfOrigin" - ); - - self.mark_invalid_message_delivery(from, topic_hash); - } - - // Reject a message. - pub(crate) fn reject_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - reason: RejectReason, - ) { - match reason { - // these messages are not tracked, but the peer is penalized as they are invalid - RejectReason::ValidationError(_) | RejectReason::SelfOrigin => { - self.reject_invalid_message(from, topic_hash); - return; - } - // we ignore those messages, so do nothing. - RejectReason::BlackListedPeer | RejectReason::BlackListedSource => { - return; - } - _ => {} // the rest are handled after record creation - } - - let peers: Vec<_> = { - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - // Multiple peers can now reject the same message as we track which peers send us the - // message. If we have already updated the status, return. - if record.status != DeliveryStatus::Unknown { - return; - } - - if let RejectReason::ValidationIgnored = reason { - // we were explicitly instructed by the validator to ignore the message but not penalize - // the peer - record.status = DeliveryStatus::Ignored; - record.peers.clear(); - return; - } - - // mark the message as invalid and penalize peers that have already forwarded it. - record.status = DeliveryStatus::Invalid; - // release the delivery time tracking map to free some memory early - record.peers.drain().collect() - }; - - self.mark_invalid_message_delivery(from, topic_hash); - for peer_id in peers.iter() { - self.mark_invalid_message_delivery(peer_id, topic_hash) - } - } - - pub(crate) fn duplicated_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - if record.peers.contains(from) { - // we have already seen this duplicate! - return; - } - - if let Some(callback) = self.message_delivery_time_callback { - let time = if let DeliveryStatus::Valid(validated) = record.status { - validated.elapsed().as_secs_f64() - } else { - 0.0 - }; - if self - .peer_stats - .get(from) - .and_then(|s| s.topics.get(topic_hash)) - .map(|ts| ts.in_mesh()) - .unwrap_or(false) - { - callback(from, topic_hash, time); - } - } - - match record.status { - DeliveryStatus::Unknown => { - // the message is being validated; track the peer delivery and wait for - // the Deliver/Reject notification. - record.peers.insert(*from); - } - DeliveryStatus::Valid(validated) => { - // mark the peer delivery time to only count a duplicate delivery once. - record.peers.insert(*from); - self.mark_duplicate_message_delivery(from, topic_hash, Some(validated)); - } - DeliveryStatus::Invalid => { - // we no longer track delivery time - self.mark_invalid_message_delivery(from, topic_hash); - } - DeliveryStatus::Ignored => { - // the message was ignored; do nothing (we don't know if it was valid) - } - } - } - - /// Sets the application specific score for a peer. Returns true if the peer is the peer is - /// connected or if the score of the peer is not yet expired and false otherwise. - pub(crate) fn set_application_score(&mut self, peer_id: &PeerId, new_score: f64) -> bool { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.application_score = new_score; - true - } else { - false - } - } - - /// Sets scoring parameters for a topic. - pub(crate) fn set_topic_params(&mut self, topic_hash: TopicHash, params: TopicScoreParams) { - use hash_map::Entry::*; - match self.params.topics.entry(topic_hash.clone()) { - Occupied(mut entry) => { - let first_message_deliveries_cap = params.first_message_deliveries_cap; - let mesh_message_deliveries_cap = params.mesh_message_deliveries_cap; - let old_params = entry.insert(params); - - if old_params.first_message_deliveries_cap > first_message_deliveries_cap { - for stats in &mut self.peer_stats.values_mut() { - if let Some(tstats) = stats.topics.get_mut(&topic_hash) { - if tstats.first_message_deliveries > first_message_deliveries_cap { - tstats.first_message_deliveries = first_message_deliveries_cap; - } - } - } - } - - if old_params.mesh_message_deliveries_cap > mesh_message_deliveries_cap { - for stats in self.peer_stats.values_mut() { - if let Some(tstats) = stats.topics.get_mut(&topic_hash) { - if tstats.mesh_message_deliveries > mesh_message_deliveries_cap { - tstats.mesh_message_deliveries = mesh_message_deliveries_cap; - } - } - } - } - } - Vacant(entry) => { - entry.insert(params); - } - } - } - - /// Returns a scoring parameters for a topic if existent. - pub(crate) fn get_topic_params(&self, topic_hash: &TopicHash) -> Option<&TopicScoreParams> { - self.params.topics.get(topic_hash) - } - - /// Increments the "invalid message deliveries" counter for all scored topics the message - /// is published in. - fn mark_invalid_message_delivery(&mut self, peer_id: &PeerId, topic_hash: &TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "[Penalty] Peer delivered an invalid message in topic and gets penalized \ - for it", - ); - topic_stats.invalid_message_deliveries += 1f64; - } - } - } - - /// Increments the "first message deliveries" counter for all scored topics the message is - /// published in, as well as the "mesh message deliveries" counter, if the peer is in the - /// mesh for the topic. - fn mark_first_message_delivery(&mut self, peer_id: &PeerId, topic_hash: &TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - let cap = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats") - .first_message_deliveries_cap; - topic_stats.first_message_deliveries = - if topic_stats.first_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.first_message_deliveries + 1f64 - }; - - if let MeshStatus::Active { .. } = topic_stats.mesh_status { - let cap = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats") - .mesh_message_deliveries_cap; - - topic_stats.mesh_message_deliveries = - if topic_stats.mesh_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.mesh_message_deliveries + 1f64 - }; - } - } - } - } - - /// Increments the "mesh message deliveries" counter for messages we've seen before, as long the - /// message was received within the P3 window. - fn mark_duplicate_message_delivery( - &mut self, - peer_id: &PeerId, - topic_hash: &TopicHash, - validated_time: Option, - ) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - let now = if validated_time.is_some() { - Some(Instant::now()) - } else { - None - }; - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - if let MeshStatus::Active { .. } = topic_stats.mesh_status { - let topic_params = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats"); - - // check against the mesh delivery window -- if the validated time is passed as 0, then - // the message was received before we finished validation and thus falls within the mesh - // delivery window. - let mut falls_in_mesh_deliver_window = true; - if let Some(validated_time) = validated_time { - if let Some(now) = &now { - //should always be true - let window_time = validated_time - .checked_add(topic_params.mesh_message_deliveries_window) - .unwrap_or(*now); - if now > &window_time { - falls_in_mesh_deliver_window = false; - } - } - } - - if falls_in_mesh_deliver_window { - let cap = topic_params.mesh_message_deliveries_cap; - topic_stats.mesh_message_deliveries = - if topic_stats.mesh_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.mesh_message_deliveries + 1f64 - }; - } - } - } - } - } - - pub(crate) fn mesh_message_deliveries(&self, peer: &PeerId, topic: &TopicHash) -> Option { - self.peer_stats - .get(peer) - .and_then(|s| s.topics.get(topic)) - .map(|t| t.mesh_message_deliveries) - } -} - -/// The reason a Gossipsub message has been rejected. -#[derive(Clone, Copy)] -pub(crate) enum RejectReason { - /// The message failed the configured validation during decoding. - ValidationError(ValidationError), - /// The message source is us. - SelfOrigin, - /// The peer that sent the message was blacklisted. - BlackListedPeer, - /// The source (from field) of the message was blacklisted. - BlackListedSource, - /// The validation was ignored. - ValidationIgnored, - /// The validation failed. - ValidationFailed, -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs deleted file mode 100644 index a5ac1b63b5..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::TopicHash; -use std::collections::{HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; - -/// The default number of seconds for a decay interval. -const DEFAULT_DECAY_INTERVAL: u64 = 1; -/// The default rate to decay to 0. -const DEFAULT_DECAY_TO_ZERO: f64 = 0.1; - -/// Computes the decay factor for a parameter, assuming the `decay_interval` is 1s -/// and that the value decays to zero if it drops below 0.01. -pub fn score_parameter_decay(decay: Duration) -> f64 { - score_parameter_decay_with_base( - decay, - Duration::from_secs(DEFAULT_DECAY_INTERVAL), - DEFAULT_DECAY_TO_ZERO, - ) -} - -/// Computes the decay factor for a parameter using base as the `decay_interval`. -pub fn score_parameter_decay_with_base(decay: Duration, base: Duration, decay_to_zero: f64) -> f64 { - // the decay is linear, so after n ticks the value is factor^n - // so factor^n = decay_to_zero => factor = decay_to_zero^(1/n) - let ticks = decay.as_secs_f64() / base.as_secs_f64(); - decay_to_zero.powf(1f64 / ticks) -} - -#[derive(Debug, Clone)] -pub struct PeerScoreThresholds { - /// The score threshold below which gossip propagation is suppressed; - /// should be negative. - pub gossip_threshold: f64, - - /// The score threshold below which we shouldn't publish when using flood - /// publishing (also applies to fanout peers); should be negative and <= `gossip_threshold`. - pub publish_threshold: f64, - - /// The score threshold below which message processing is suppressed altogether, - /// implementing an effective graylist according to peer score; should be negative and - /// <= `publish_threshold`. - pub graylist_threshold: f64, - - /// The score threshold below which px will be ignored; this should be positive - /// and limited to scores attainable by bootstrappers and other trusted nodes. - pub accept_px_threshold: f64, - - /// The median mesh score threshold before triggering opportunistic - /// grafting; this should have a small positive value. - pub opportunistic_graft_threshold: f64, -} - -impl Default for PeerScoreThresholds { - fn default() -> Self { - PeerScoreThresholds { - gossip_threshold: -10.0, - publish_threshold: -50.0, - graylist_threshold: -80.0, - accept_px_threshold: 10.0, - opportunistic_graft_threshold: 20.0, - } - } -} - -impl PeerScoreThresholds { - pub fn validate(&self) -> Result<(), &'static str> { - if self.gossip_threshold > 0f64 { - return Err("invalid gossip threshold; it must be <= 0"); - } - if self.publish_threshold > 0f64 || self.publish_threshold > self.gossip_threshold { - return Err("Invalid publish threshold; it must be <= 0 and <= gossip threshold"); - } - if self.graylist_threshold > 0f64 || self.graylist_threshold > self.publish_threshold { - return Err("Invalid graylist threshold; it must be <= 0 and <= publish threshold"); - } - if self.accept_px_threshold < 0f64 { - return Err("Invalid accept px threshold; it must be >= 0"); - } - if self.opportunistic_graft_threshold < 0f64 { - return Err("Invalid opportunistic grafting threshold; it must be >= 0"); - } - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct PeerScoreParams { - /// Score parameters per topic. - pub topics: HashMap, - - /// Aggregate topic score cap; this limits the total contribution of topics towards a positive - /// score. It must be positive (or 0 for no cap). - pub topic_score_cap: f64, - - /// P5: Application-specific peer scoring - pub app_specific_weight: f64, - - /// P6: IP-colocation factor. - /// The parameter has an associated counter which counts the number of peers with the same IP. - /// If the number of peers in the same IP exceeds `ip_colocation_factor_threshold, then the value - /// is the square of the difference, ie `(peers_in_same_ip - ip_colocation_threshold)^2`. - /// If the number of peers in the same IP is less than the threshold, then the value is 0. - /// The weight of the parameter MUST be negative, unless you want to disable for testing. - /// Note: In order to simulate many IPs in a manageable manner when testing, you can set the weight to 0 - /// thus disabling the IP colocation penalty. - pub ip_colocation_factor_weight: f64, - pub ip_colocation_factor_threshold: f64, - pub ip_colocation_factor_whitelist: HashSet, - - /// P7: behavioural pattern penalties. - /// This parameter has an associated counter which tracks misbehaviour as detected by the - /// router. The router currently applies penalties for the following behaviors: - /// - attempting to re-graft before the prune backoff time has elapsed. - /// - not following up in IWANT requests for messages advertised with IHAVE. - /// - /// The value of the parameter is the square of the counter over the threshold, which decays - /// with BehaviourPenaltyDecay. - /// The weight of the parameter MUST be negative (or zero to disable). - pub behaviour_penalty_weight: f64, - pub behaviour_penalty_threshold: f64, - pub behaviour_penalty_decay: f64, - - /// The decay interval for parameter counters. - pub decay_interval: Duration, - - /// Counter value below which it is considered 0. - pub decay_to_zero: f64, - - /// Time to remember counters for a disconnected peer. - pub retain_score: Duration, - - /// Slow peer penalty conditions - pub slow_peer_weight: f64, - pub slow_peer_threshold: f64, - pub slow_peer_decay: f64, -} - -impl Default for PeerScoreParams { - fn default() -> Self { - PeerScoreParams { - topics: HashMap::new(), - topic_score_cap: 3600.0, - app_specific_weight: 10.0, - ip_colocation_factor_weight: -5.0, - ip_colocation_factor_threshold: 10.0, - ip_colocation_factor_whitelist: HashSet::new(), - behaviour_penalty_weight: -10.0, - behaviour_penalty_threshold: 0.0, - behaviour_penalty_decay: 0.2, - decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL), - decay_to_zero: DEFAULT_DECAY_TO_ZERO, - retain_score: Duration::from_secs(3600), - slow_peer_weight: -0.2, - slow_peer_threshold: 0.0, - slow_peer_decay: 0.2, - } - } -} - -/// Peer score parameter validation -impl PeerScoreParams { - pub fn validate(&self) -> Result<(), String> { - for (topic, params) in self.topics.iter() { - if let Err(e) = params.validate() { - return Err(format!("Invalid score parameters for topic {topic}: {e}")); - } - } - - // check that the topic score is 0 or something positive - if self.topic_score_cap < 0f64 { - return Err("Invalid topic score cap; must be positive (or 0 for no cap)".into()); - } - - // check the IP colocation factor - if self.ip_colocation_factor_weight > 0f64 { - return Err( - "Invalid ip_colocation_factor_weight; must be negative (or 0 to disable)".into(), - ); - } - if self.ip_colocation_factor_weight != 0f64 && self.ip_colocation_factor_threshold < 1f64 { - return Err("Invalid ip_colocation_factor_threshold; must be at least 1".into()); - } - - // check the behaviour penalty - if self.behaviour_penalty_weight > 0f64 { - return Err( - "Invalid behaviour_penalty_weight; must be negative (or 0 to disable)".into(), - ); - } - if self.behaviour_penalty_weight != 0f64 - && (self.behaviour_penalty_decay <= 0f64 || self.behaviour_penalty_decay >= 1f64) - { - return Err("invalid behaviour_penalty_decay; must be between 0 and 1".into()); - } - - if self.behaviour_penalty_threshold < 0f64 { - return Err("invalid behaviour_penalty_threshold; must be >= 0".into()); - } - - // check the decay parameters - if self.decay_interval < Duration::from_secs(1) { - return Err("Invalid decay_interval; must be at least 1s".into()); - } - if self.decay_to_zero <= 0f64 || self.decay_to_zero >= 1f64 { - return Err("Invalid decay_to_zero; must be between 0 and 1".into()); - } - - // no need to check the score retention; a value of 0 means that we don't retain scores - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct TopicScoreParams { - /// The weight of the topic. - pub topic_weight: f64, - - /// P1: time in the mesh - /// This is the time the peer has been grafted in the mesh. - /// The value of of the parameter is the `time/time_in_mesh_quantum`, capped by `time_in_mesh_cap` - /// The weight of the parameter must be positive (or zero to disable). - pub time_in_mesh_weight: f64, - pub time_in_mesh_quantum: Duration, - pub time_in_mesh_cap: f64, - - /// P2: first message deliveries - /// This is the number of message deliveries in the topic. - /// The value of the parameter is a counter, decaying with `first_message_deliveries_decay`, and capped - /// by `first_message_deliveries_cap`. - /// The weight of the parameter MUST be positive (or zero to disable). - pub first_message_deliveries_weight: f64, - pub first_message_deliveries_decay: f64, - pub first_message_deliveries_cap: f64, - - /// P3: mesh message deliveries - /// This is the number of message deliveries in the mesh, within the - /// `mesh_message_deliveries_window` of message validation; deliveries during validation also - /// count and are retroactively applied when validation succeeds. - /// This window accounts for the minimum time before a hostile mesh peer trying to game the - /// score could replay back a valid message we just sent them. - /// It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer - /// before we have forwarded it to them. - /// The parameter has an associated counter, decaying with `mesh_message_deliveries_decay`. - /// If the counter exceeds the threshold, its value is 0. - /// If the counter is below the `mesh_message_deliveries_threshold`, the value is the square of - /// the deficit, ie (`message_deliveries_threshold - counter)^2` - /// The penalty is only activated after `mesh_message_deliveries_activation` time in the mesh. - /// The weight of the parameter MUST be negative (or zero to disable). - pub mesh_message_deliveries_weight: f64, - pub mesh_message_deliveries_decay: f64, - pub mesh_message_deliveries_cap: f64, - pub mesh_message_deliveries_threshold: f64, - pub mesh_message_deliveries_window: Duration, - pub mesh_message_deliveries_activation: Duration, - - /// P3b: sticky mesh propagation failures - /// This is a sticky penalty that applies when a peer gets pruned from the mesh with an active - /// mesh message delivery penalty. - /// The weight of the parameter MUST be negative (or zero to disable) - pub mesh_failure_penalty_weight: f64, - pub mesh_failure_penalty_decay: f64, - - /// P4: invalid messages - /// This is the number of invalid messages in the topic. - /// The value of the parameter is the square of the counter, decaying with - /// `invalid_message_deliveries_decay`. - /// The weight of the parameter MUST be negative (or zero to disable). - pub invalid_message_deliveries_weight: f64, - pub invalid_message_deliveries_decay: f64, -} - -/// NOTE: The topic score parameters are very network specific. -/// For any production system, these values should be manually set. -impl Default for TopicScoreParams { - fn default() -> Self { - TopicScoreParams { - topic_weight: 0.5, - // P1 - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 3600.0, - // P2 - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 0.5, - first_message_deliveries_cap: 2000.0, - // P3 - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_decay: 0.5, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_activation: Duration::from_secs(5), - // P3b - mesh_failure_penalty_weight: -1.0, - mesh_failure_penalty_decay: 0.5, - // P4 - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 0.3, - } - } -} - -impl TopicScoreParams { - pub fn validate(&self) -> Result<(), &'static str> { - // make sure we have a sane topic weight - if self.topic_weight < 0f64 { - return Err("invalid topic weight; must be >= 0"); - } - - if self.time_in_mesh_quantum == Duration::from_secs(0) { - return Err("Invalid time_in_mesh_quantum; must be non zero"); - } - if self.time_in_mesh_weight < 0f64 { - return Err("Invalid time_in_mesh_weight; must be positive (or 0 to disable)"); - } - if self.time_in_mesh_weight != 0f64 && self.time_in_mesh_cap <= 0f64 { - return Err("Invalid time_in_mesh_cap must be positive"); - } - - if self.first_message_deliveries_weight < 0f64 { - return Err( - "Invalid first_message_deliveries_weight; must be positive (or 0 to disable)", - ); - } - if self.first_message_deliveries_weight != 0f64 - && (self.first_message_deliveries_decay <= 0f64 - || self.first_message_deliveries_decay >= 1f64) - { - return Err("Invalid first_message_deliveries_decay; must be between 0 and 1"); - } - if self.first_message_deliveries_weight != 0f64 && self.first_message_deliveries_cap <= 0f64 - { - return Err("Invalid first_message_deliveries_cap must be positive"); - } - - if self.mesh_message_deliveries_weight > 0f64 { - return Err( - "Invalid mesh_message_deliveries_weight; must be negative (or 0 to disable)", - ); - } - if self.mesh_message_deliveries_weight != 0f64 - && (self.mesh_message_deliveries_decay <= 0f64 - || self.mesh_message_deliveries_decay >= 1f64) - { - return Err("Invalid mesh_message_deliveries_decay; must be between 0 and 1"); - } - if self.mesh_message_deliveries_weight != 0f64 && self.mesh_message_deliveries_cap <= 0f64 { - return Err("Invalid mesh_message_deliveries_cap must be positive"); - } - if self.mesh_message_deliveries_weight != 0f64 - && self.mesh_message_deliveries_threshold <= 0f64 - { - return Err("Invalid mesh_message_deliveries_threshold; must be positive"); - } - if self.mesh_message_deliveries_weight != 0f64 - && self.mesh_message_deliveries_activation < Duration::from_secs(1) - { - return Err("Invalid mesh_message_deliveries_activation; must be at least 1s"); - } - - // check P3b - if self.mesh_failure_penalty_weight > 0f64 { - return Err("Invalid mesh_failure_penalty_weight; must be negative (or 0 to disable)"); - } - if self.mesh_failure_penalty_weight != 0f64 - && (self.mesh_failure_penalty_decay <= 0f64 || self.mesh_failure_penalty_decay >= 1f64) - { - return Err("Invalid mesh_failure_penalty_decay; must be between 0 and 1"); - } - - // check P4 - if self.invalid_message_deliveries_weight > 0f64 { - return Err( - "Invalid invalid_message_deliveries_weight; must be negative (or 0 to disable)", - ); - } - if self.invalid_message_deliveries_decay <= 0f64 - || self.invalid_message_deliveries_decay >= 1f64 - { - return Err("Invalid invalid_message_deliveries_decay; must be between 0 and 1"); - } - Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs deleted file mode 100644 index 064e277eed..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -/// A collection of unit tests mostly ported from the go implementation. -use super::*; - -use crate::types::RawMessage; -use crate::{IdentTopic as Topic, Message}; - -// estimates a value within variance -fn within_variance(value: f64, expected: f64, variance: f64) -> bool { - if expected >= 0.0 { - return value > expected * (1.0 - variance) && value < expected * (1.0 + variance); - } - value > expected * (1.0 + variance) && value < expected * (1.0 - variance) -} - -// generates a random gossipsub message with sequence number i -fn make_test_message(seq: u64) -> (MessageId, RawMessage) { - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![12, 34, 56], - sequence_number: Some(seq), - topic: Topic::new("test").hash(), - signature: None, - key: None, - validated: true, - }; - - let message = Message { - source: raw_message.source, - data: raw_message.data.clone(), - sequence_number: raw_message.sequence_number, - topic: raw_message.topic.clone(), - }; - - let id = default_message_id()(&message); - (id, raw_message) -} - -fn default_message_id() -> fn(&Message) -> MessageId { - |message| { - // default message id is: source + sequence number - // NOTE: If either the peer_id or source is not provided, we set to 0; - let mut source_string = if let Some(peer_id) = message.source.as_ref() { - peer_id.to_base58() - } else { - PeerId::from_bytes(&[0, 1, 0]) - .expect("Valid peer id") - .to_base58() - }; - source_string.push_str(&message.sequence_number.unwrap_or_default().to_string()); - MessageId::from(source_string) - } -} - -#[test] -fn test_score_time_in_mesh() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - topic_score_cap: 1000.0, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 0.5, - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 3600.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - - let score = peer_score.score(&peer_id); - assert!( - score == 0.0, - "expected score to start at zero. Score found: {score}" - ); - - // The time in mesh depends on how long the peer has been grafted - peer_score.graft(&peer_id, topic); - let elapsed = topic_params.time_in_mesh_quantum * 200; - std::thread::sleep(elapsed); - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.time_in_mesh_weight - * (elapsed.as_millis() / topic_params.time_in_mesh_quantum.as_millis()) as f64; - assert!( - score >= expected, - "The score: {score} should be greater than or equal to: {expected}" - ); -} - -#[test] -fn test_score_time_in_mesh_cap() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 0.5, - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 10.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - - let score = peer_score.score(&peer_id); - assert!( - score == 0.0, - "expected score to start at zero. Score found: {score}" - ); - - // The time in mesh depends on how long the peer has been grafted - peer_score.graft(&peer_id, topic); - let elapsed = topic_params.time_in_mesh_quantum * 40; - std::thread::sleep(elapsed); - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.time_in_mesh_weight - * topic_params.time_in_mesh_cap; - let variance = 0.5; - assert!( - within_variance(score, expected, variance), - "The score: {} should be within {} of {}", - score, - score * variance, - expected - ); -} - -#[test] -fn test_score_first_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 1.0, - first_message_deliveries_cap: 2000.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = - topic_params.topic_weight * topic_params.first_message_deliveries_weight * messages as f64; - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_first_message_deliveries_cap() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 1.0, // test without decay - first_message_deliveries_cap: 50.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.first_message_deliveries_weight - * topic_params.first_message_deliveries_cap; - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_first_message_deliveries_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 0.9, // decay 10% per decay interval - first_message_deliveries_cap: 2000.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let peer_id = PeerId::random(); - let mut peer_score = PeerScore::new(params); - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score = peer_score.score(&peer_id); - let mut expected = topic_params.topic_weight - * topic_params.first_message_deliveries_weight - * topic_params.first_message_deliveries_decay - * messages as f64; - assert!(score == expected, "The score: {score} should be {expected}"); - - // refreshing the scores applies the decay param - let decay_intervals = 10; - for _ in 0..decay_intervals { - peer_score.refresh_scores(); - expected *= topic_params.first_message_deliveries_decay; - } - let score = peer_score.score(&peer_id); - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_mesh_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - // peer A always delivers the message first. - // peer B delivers next (within the delivery window). - // peer C delivers outside the delivery window. - // we expect peers A and B to have a score of zero, since all other parameter weights are zero. - // Peer C should have a negative score. - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - let peer_id_c = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b, peer_id_c]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // assert that nobody has been penalized yet for not delivering messages before activation time - peer_score.refresh_scores(); - for peer_id in &peers { - let score = peer_score.score(peer_id); - assert!( - score >= 0.0, - "expected no mesh delivery penalty before activation time, got score {score}" - ); - } - - // wait for the activation time to kick in - std::thread::sleep(topic_params.mesh_message_deliveries_activation); - - // deliver a bunch of messages from peer A, with duplicates within the window from peer B, - // and duplicates outside the window from peer C. - let messages = 100; - let mut messages_to_send = Vec::new(); - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - messages_to_send.push((id, msg)); - } - - std::thread::sleep(topic_params.mesh_message_deliveries_window + Duration::from_millis(20)); - - for (id, msg) in messages_to_send { - peer_score.duplicated_message(&peer_id_c, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - let score_c = peer_score.score(&peer_id_c); - - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - assert!( - score_b >= 0.0, - "expected non-negative score for Peer B, got score {score_b}" - ); - - // the penalty is the difference between the threshold and the actual mesh deliveries, squared. - // since we didn't deliver anything, this is just the value of the threshold - let penalty = topic_params.mesh_message_deliveries_threshold - * topic_params.mesh_message_deliveries_threshold; - let expected = - topic_params.topic_weight * topic_params.mesh_message_deliveries_weight * penalty; - - assert!(score_c == expected, "Score: {score_c}. Expected {expected}"); -} - -#[test] -fn test_score_mesh_message_deliveries_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 0.9, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - mesh_failure_penalty_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // deliver a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - } - - // we should have a positive score, since we delivered more messages than the threshold - peer_score.refresh_scores(); - - let score_a = peer_score.score(&peer_id_a); - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - - let mut decayed_delivery_count = (messages as f64) * topic_params.mesh_message_deliveries_decay; - for _ in 0..20 { - peer_score.refresh_scores(); - decayed_delivery_count *= topic_params.mesh_message_deliveries_decay; - } - - let score_a = peer_score.score(&peer_id_a); - // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. - let deficit = topic_params.mesh_message_deliveries_threshold - decayed_delivery_count; - let penalty = deficit * deficit; - let expected = - topic_params.topic_weight * topic_params.mesh_message_deliveries_weight * penalty; - - assert_eq!(score_a, expected, "Invalid score"); -} - -#[test] -fn test_score_mesh_failure_penalty() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - // the mesh failure penalty is applied when a peer is pruned while their - // mesh deliveries are under the threshold. - // for this test, we set the mesh delivery threshold, but set - // mesh_message_deliveries to zero, so the only affect on the score - // is from the mesh failure penalty - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - mesh_failure_penalty_weight: -1.0, - mesh_failure_penalty_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // deliver a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - } - - // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - assert!( - score_b >= 0.0, - "expected non-negative score for Peer B, got score {score_b}" - ); - - // prune peer B to apply the penalty - peer_score.prune(&peer_id_b, topic.hash()); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - - assert_eq!(score_a, 0.0, "expected Peer A to have a 0"); - - // penalty calculation is the same as for mesh_message_deliveries, but multiplied by - // mesh_failure_penalty_weigh - // instead of mesh_message_deliveries_weight - let penalty = topic_params.mesh_message_deliveries_threshold - * topic_params.mesh_message_deliveries_threshold; - let expected = topic_params.topic_weight * topic_params.mesh_failure_penalty_weight * penalty; - - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_b, expected, "Peer B should have expected score",); -} - -#[test] -fn test_score_invalid_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // reject a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - } - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - - let expected = topic_params.topic_weight - * topic_params.invalid_message_deliveries_weight - * (messages * messages) as f64; - - assert_eq!(score_a, expected, "Peer has unexpected score",); -} - -#[test] -fn test_score_invalid_message_deliveris_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 0.9, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // reject a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - } - - peer_score.refresh_scores(); - - let decay = topic_params.invalid_message_deliveries_decay * messages as f64; - - let mut expected = - topic_params.topic_weight * topic_params.invalid_message_deliveries_weight * decay * decay; - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, expected, "Peer has unexpected score"); - - // refresh scores a few times to apply decay - for _ in 0..10 { - peer_score.refresh_scores(); - expected *= topic_params.invalid_message_deliveries_decay - * topic_params.invalid_message_deliveries_decay; - } - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, expected, "Peer has unexpected score"); -} - -#[test] -fn test_score_reject_message_deliveries() { - // This tests adds coverage for the dark corners of rejection tracing - - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - } - - let (id, msg) = make_test_message(1); - - // these should have no effect in the score - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::BlackListedPeer); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::BlackListedSource); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // insert a record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // this should have no effect in the score, and subsequent duplicate messages should have no - // effect either - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // now clear the delivery record - peer_score.deliveries.clear(); - - // insert a record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // this should have no effect in the score, and subsequent duplicate messages should have no - // effect either - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // now clear the delivery record - peer_score.deliveries.clear(); - - // insert a new record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // and reject the message to make sure duplicates are also penalized - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, -1.0, "Score should be effected"); - assert_eq!(score_b, -1.0, "Score should be effected"); - - // now clear the delivery record again - peer_score.deliveries.clear(); - - // insert a new record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // and reject the message after a duplicate has arrived - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, -4.0, "Score should be effected"); - assert_eq!(score_b, -4.0, "Score should be effected"); -} - -#[test] -fn test_application_score() { - // Create parameters with reasonable default values - let app_specific_weight = 0.5; - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - app_specific_weight, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - let messages = 100; - for i in -100..messages { - let app_score_value = i as f64; - peer_score.set_application_score(&peer_id_a, app_score_value); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let expected = (i as f64) * app_specific_weight; - assert_eq!(score_a, expected, "Peer has unexpected score"); - } -} - -#[test] -fn test_score_ip_colocation() { - // Create parameters with reasonable default values - let ip_colocation_factor_weight = -1.0; - let ip_colocation_factor_threshold = 1.0; - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - ip_colocation_factor_weight, - ip_colocation_factor_threshold, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - let peer_id_c = PeerId::random(); - let peer_id_d = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b, peer_id_c, peer_id_d]; - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP - peer_score.add_ip(&peer_id_a, "1.2.3.4".parse().unwrap()); - peer_score.add_ip(&peer_id_b, "2.3.4.5".parse().unwrap()); - peer_score.add_ip(&peer_id_c, "2.3.4.5".parse().unwrap()); - peer_score.add_ip(&peer_id_c, "3.4.5.6".parse().unwrap()); - peer_score.add_ip(&peer_id_d, "2.3.4.5".parse().unwrap()); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - let score_c = peer_score.score(&peer_id_c); - let score_d = peer_score.score(&peer_id_d); - - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - let n_shared = 3.0; - let ip_surplus = n_shared - ip_colocation_factor_threshold; - let penalty = ip_surplus * ip_surplus; - let expected = ip_colocation_factor_weight * penalty; - - assert_eq!(score_b, expected, "Peer B should have expected score"); - assert_eq!(score_c, expected, "Peer C should have expected score"); - assert_eq!(score_d, expected, "Peer D should have expected score"); -} - -#[test] -fn test_score_behaviour_penality() { - // Create parameters with reasonable default values - let behaviour_penalty_weight = -1.0; - let behaviour_penalty_decay = 0.99; - - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - behaviour_penalty_decay, - behaviour_penalty_weight, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - - // add a penalty to a non-existent peer. - peer_score.add_penalty(&peer_id_a, 1); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - // add the peer and test penalties - peer_score.add_peer(peer_id_a); - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - peer_score.add_penalty(&peer_id_a, 1); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -1.0, "Peer A should have been penalized"); - - peer_score.add_penalty(&peer_id_a, 1); - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -4.0, "Peer A should have been penalized"); - - peer_score.refresh_scores(); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -3.9204, "Peer A should have been penalized"); -} - -#[test] -fn test_score_retention() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let app_specific_weight = 1.0; - let app_score_value = -1000.0; - let retain_score = Duration::from_secs(1); - let mut params = PeerScoreParams { - app_specific_weight, - retain_score, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 0.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - peer_score.set_application_score(&peer_id_a, app_score_value); - - // score should equal -1000 (app specific score) - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, app_score_value, - "Score should be the application specific score" - ); - - // disconnect & wait half of RetainScore time. Should still have negative score - peer_score.remove_peer(&peer_id_a); - std::thread::sleep(retain_score / 2); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, app_score_value, - "Score should be the application specific score" - ); - - // wait remaining time (plus a little slop) and the score should reset to zero - std::thread::sleep(retain_score / 2 + Duration::from_millis(50)); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, 0.0, - "Score should be the application specific score" - ); -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/protocol.rs b/beacon_node/lighthouse_network/gossipsub/src/protocol.rs deleted file mode 100644 index b72f4ccc9b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/protocol.rs +++ /dev/null @@ -1,646 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::config::ValidationMode; -use super::handler::HandlerEvent; -use super::rpc_proto::proto; -use super::topic::TopicHash; -use super::types::{ - ControlAction, Graft, IDontWant, IHave, IWant, MessageId, PeerInfo, PeerKind, Prune, - RawMessage, Rpc, Subscription, SubscriptionAction, -}; -use super::ValidationError; -use asynchronous_codec::{Decoder, Encoder, Framed}; -use byteorder::{BigEndian, ByteOrder}; -use bytes::BytesMut; -use futures::prelude::*; -use libp2p::core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p::identity::{PeerId, PublicKey}; -use libp2p::swarm::StreamProtocol; -use quick_protobuf::Writer; -use std::pin::Pin; -use void::Void; - -pub(crate) const SIGNING_PREFIX: &[u8] = b"libp2p-pubsub:"; - -pub(crate) const GOSSIPSUB_1_2_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.2.0"), - kind: PeerKind::Gossipsubv1_2, -}; -pub(crate) const GOSSIPSUB_1_1_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.1.0"), - kind: PeerKind::Gossipsubv1_1, -}; -pub(crate) const GOSSIPSUB_1_0_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.0.0"), - kind: PeerKind::Gossipsub, -}; -pub(crate) const FLOODSUB_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/floodsub/1.0.0"), - kind: PeerKind::Floodsub, -}; - -/// Implementation of [`InboundUpgrade`] and [`OutboundUpgrade`] for the Gossipsub protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - /// The Gossipsub protocol id to listen on. - pub(crate) protocol_ids: Vec, - /// The maximum transmit size for a packet. - pub(crate) max_transmit_size: usize, - /// Determines the level of validation to be done on incoming messages. - pub(crate) validation_mode: ValidationMode, -} - -impl Default for ProtocolConfig { - fn default() -> Self { - Self { - max_transmit_size: 65536, - validation_mode: ValidationMode::Strict, - protocol_ids: vec![ - GOSSIPSUB_1_2_0_PROTOCOL, - GOSSIPSUB_1_1_0_PROTOCOL, - GOSSIPSUB_1_0_0_PROTOCOL, - ], - } - } -} - -/// The protocol ID -#[derive(Clone, Debug, PartialEq)] -pub struct ProtocolId { - /// The RPC message type/name. - pub protocol: StreamProtocol, - /// The type of protocol we support - pub kind: PeerKind, -} - -impl AsRef for ProtocolId { - fn as_ref(&self) -> &str { - self.protocol.as_ref() - } -} - -impl UpgradeInfo for ProtocolConfig { - type Info = ProtocolId; - type InfoIter = Vec; - - fn protocol_info(&self) -> Self::InfoIter { - self.protocol_ids.clone() - } -} - -impl InboundUpgrade for ProtocolConfig -where - TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - type Output = (Framed, PeerKind); - type Error = Void; - type Future = Pin> + Send>>; - - fn upgrade_inbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { - Box::pin(future::ok(( - Framed::new( - socket, - GossipsubCodec::new(self.max_transmit_size, self.validation_mode), - ), - protocol_id.kind, - ))) - } -} - -impl OutboundUpgrade for ProtocolConfig -where - TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, -{ - type Output = (Framed, PeerKind); - type Error = Void; - type Future = Pin> + Send>>; - - fn upgrade_outbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { - Box::pin(future::ok(( - Framed::new( - socket, - GossipsubCodec::new(self.max_transmit_size, self.validation_mode), - ), - protocol_id.kind, - ))) - } -} - -/* Gossip codec for the framing */ - -pub struct GossipsubCodec { - /// Determines the level of validation performed on incoming messages. - validation_mode: ValidationMode, - /// The codec to handle common encoding/decoding of protobuf messages - codec: quick_protobuf_codec::Codec, -} - -impl GossipsubCodec { - pub fn new(max_length: usize, validation_mode: ValidationMode) -> GossipsubCodec { - let codec = quick_protobuf_codec::Codec::new(max_length); - GossipsubCodec { - validation_mode, - codec, - } - } - - /// Verifies a gossipsub message. This returns either a success or failure. All errors - /// are logged, which prevents error handling in the codec and handler. We simply drop invalid - /// messages and log warnings, rather than propagating errors through the codec. - fn verify_signature(message: &proto::Message) -> bool { - use quick_protobuf::MessageWrite; - - let Some(from) = message.from.as_ref() else { - tracing::debug!("Signature verification failed: No source id given"); - return false; - }; - - let Ok(source) = PeerId::from_bytes(from) else { - tracing::debug!("Signature verification failed: Invalid Peer Id"); - return false; - }; - - let Some(signature) = message.signature.as_ref() else { - tracing::debug!("Signature verification failed: No signature provided"); - return false; - }; - - // If there is a key value in the protobuf, use that key otherwise the key must be - // obtained from the inlined source peer_id. - let public_key = match message.key.as_deref().map(PublicKey::try_decode_protobuf) { - Some(Ok(key)) => key, - _ => match PublicKey::try_decode_protobuf(&source.to_bytes()[2..]) { - Ok(v) => v, - Err(_) => { - tracing::warn!("Signature verification failed: No valid public key supplied"); - return false; - } - }, - }; - - // The key must match the peer_id - if source != public_key.to_peer_id() { - tracing::warn!( - "Signature verification failed: Public key doesn't match source peer id" - ); - return false; - } - - // Construct the signature bytes - let mut message_sig = message.clone(); - message_sig.signature = None; - message_sig.key = None; - let mut buf = Vec::with_capacity(message_sig.get_size()); - let mut writer = Writer::new(&mut buf); - message_sig - .write_message(&mut writer) - .expect("Encoding to succeed"); - let mut signature_bytes = SIGNING_PREFIX.to_vec(); - signature_bytes.extend_from_slice(&buf); - public_key.verify(&signature_bytes, signature) - } -} - -impl Encoder for GossipsubCodec { - type Item<'a> = proto::RPC; - type Error = quick_protobuf_codec::Error; - - fn encode(&mut self, item: Self::Item<'_>, dst: &mut BytesMut) -> Result<(), Self::Error> { - self.codec.encode(item, dst) - } -} - -impl Decoder for GossipsubCodec { - type Item = HandlerEvent; - type Error = quick_protobuf_codec::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let Some(rpc) = self.codec.decode(src)? else { - return Ok(None); - }; - // Store valid messages. - let mut messages = Vec::with_capacity(rpc.publish.len()); - // Store any invalid messages. - let mut invalid_messages = Vec::new(); - - for message in rpc.publish.into_iter() { - // Keep track of the type of invalid message. - let mut invalid_kind = None; - let mut verify_signature = false; - let mut verify_sequence_no = false; - let mut verify_source = false; - - match self.validation_mode { - ValidationMode::Strict => { - // Validate everything - verify_signature = true; - verify_sequence_no = true; - verify_source = true; - } - ValidationMode::Permissive => { - // If the fields exist, validate them - if message.signature.is_some() { - verify_signature = true; - } - if message.seqno.is_some() { - verify_sequence_no = true; - } - if message.from.is_some() { - verify_source = true; - } - } - ValidationMode::Anonymous => { - if message.signature.is_some() { - tracing::warn!( - "Signature field was non-empty and anonymous validation mode is set" - ); - invalid_kind = Some(ValidationError::SignaturePresent); - } else if message.seqno.is_some() { - tracing::warn!( - "Sequence number was non-empty and anonymous validation mode is set" - ); - invalid_kind = Some(ValidationError::SequenceNumberPresent); - } else if message.from.is_some() { - tracing::warn!("Message dropped. Message source was non-empty and anonymous validation mode is set"); - invalid_kind = Some(ValidationError::MessageSourcePresent); - } - } - ValidationMode::None => {} - } - - // If the initial validation logic failed, add the message to invalid messages and - // continue processing the others. - if let Some(validation_error) = invalid_kind.take() { - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: None, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, validation_error)); - // proceed to the next message - continue; - } - - // verify message signatures if required - if verify_signature && !GossipsubCodec::verify_signature(&message) { - tracing::warn!("Invalid signature for received message"); - - // Build the invalid message (ignoring further validation of sequence number - // and source) - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: None, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidSignature)); - // proceed to the next message - continue; - } - - // ensure the sequence number is a u64 - let sequence_number = if verify_sequence_no { - if let Some(seq_no) = message.seqno { - if seq_no.is_empty() { - None - } else if seq_no.len() != 8 { - tracing::debug!( - sequence_number=?seq_no, - sequence_length=%seq_no.len(), - "Invalid sequence number length for received message" - ); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidSequenceNumber)); - // proceed to the next message - continue; - } else { - // valid sequence number - Some(BigEndian::read_u64(&seq_no)) - } - } else { - // sequence number was not present - tracing::debug!("Sequence number not present but expected"); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::EmptySequenceNumber)); - continue; - } - } else { - // Do not verify the sequence number, consider it empty - None - }; - - // Verify the message source if required - let source = if verify_source { - if let Some(bytes) = message.from { - if !bytes.is_empty() { - match PeerId::from_bytes(&bytes) { - Ok(peer_id) => Some(peer_id), // valid peer id - Err(_) => { - // invalid peer id, add to invalid messages - tracing::debug!("Message source has an invalid PeerId"); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number, - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidPeerId)); - continue; - } - } - } else { - None - } - } else { - None - } - } else { - None - }; - - // This message has passed all validation, add it to the validated messages. - messages.push(RawMessage { - source, - data: message.data.unwrap_or_default(), - sequence_number, - topic: TopicHash::from_raw(message.topic), - signature: message.signature, - key: message.key, - validated: false, - }); - } - - let mut control_msgs = Vec::new(); - - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| { - ControlAction::IHave(IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| { - ControlAction::IWant(IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| { - ControlAction::Graft(Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - }) - .collect(); - - let mut prune_msgs = Vec::new(); - - for prune in rpc_control.prune { - // filter out invalid peers - let peers = prune - .peers - .into_iter() - .filter_map(|info| { - info.peer_id - .as_ref() - .and_then(|id| PeerId::from_bytes(id).ok()) - .map(|peer_id| - //TODO signedPeerRecord, see https://github.com/libp2p/specs/pull/217 - PeerInfo { - peer_id: Some(peer_id), - }) - }) - .collect::>(); - - let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune(Prune { - topic_hash, - peers, - backoff: prune.backoff, - })); - } - - let idontwant_msgs: Vec = rpc_control - .idontwant - .into_iter() - .map(|idontwant| { - ControlAction::IDontWant(IDontWant { - message_ids: idontwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - control_msgs.extend(idontwant_msgs); - } - - Ok(Some(HandlerEvent::Message { - rpc: Rpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| Subscription { - action: if Some(true) == sub.subscribe { - SubscriptionAction::Subscribe - } else { - SubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - }, - invalid_messages, - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::config::Config; - use crate::{Behaviour, ConfigBuilder, MessageAuthenticity}; - use crate::{IdentTopic as Topic, Version}; - use libp2p::identity::Keypair; - use quickcheck::*; - - #[derive(Clone, Debug)] - struct Message(RawMessage); - - impl Arbitrary for Message { - fn arbitrary(g: &mut Gen) -> Self { - let keypair = TestKeypair::arbitrary(g); - - // generate an arbitrary GossipsubMessage using the behaviour signing functionality - let config = Config::default(); - let mut gs: Behaviour = - Behaviour::new(MessageAuthenticity::Signed(keypair.0), config).unwrap(); - let mut data_g = quickcheck::Gen::new(10024); - let data = (0..u8::arbitrary(&mut data_g)) - .map(|_| u8::arbitrary(g)) - .collect::>(); - let topic_id = TopicId::arbitrary(g).0; - Message(gs.build_raw_message(topic_id, data).unwrap()) - } - } - - #[derive(Clone, Debug)] - struct TopicId(TopicHash); - - impl Arbitrary for TopicId { - fn arbitrary(g: &mut Gen) -> Self { - let mut data_g = quickcheck::Gen::new(1024); - let topic_string: String = (0..u8::arbitrary(&mut data_g)) - .map(|_| char::arbitrary(g)) - .collect::(); - TopicId(Topic::new(topic_string).into()) - } - } - - #[derive(Clone)] - struct TestKeypair(Keypair); - - impl Arbitrary for TestKeypair { - #[cfg(feature = "rsa")] - fn arbitrary(g: &mut Gen) -> Self { - let keypair = if bool::arbitrary(g) { - // Small enough to be inlined. - Keypair::generate_ed25519() - } else { - // Too large to be inlined. - let mut rsa_key = hex::decode("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100ef930f41a71288b643c1cbecbf5f72ab53992249e2b00835bf07390b6745419f3848cbcc5b030faa127bc88cdcda1c1d6f3ff699f0524c15ab9d2c9d8015f5d4bd09881069aad4e9f91b8b0d2964d215cdbbae83ddd31a7622a8228acee07079f6e501aea95508fa26c6122816ef7b00ac526d422bd12aed347c37fff6c1c307f3ba57bb28a7f28609e0bdcc839da4eedca39f5d2fa855ba4b0f9c763e9764937db929a1839054642175312a3de2d3405c9d27bdf6505ef471ce85c5e015eee85bf7874b3d512f715de58d0794fd8afe021c197fbd385bb88a930342fac8da31c27166e2edab00fa55dc1c3814448ba38363077f4e8fe2bdea1c081f85f1aa6f02030100010282010028ff427a1aac1a470e7b4879601a6656193d3857ea79f33db74df61e14730e92bf9ffd78200efb0c40937c3356cbe049cd32e5f15be5c96d5febcaa9bd3484d7fded76a25062d282a3856a1b3b7d2c525cdd8434beae147628e21adf241dd64198d5819f310d033743915ba40ea0b6acdbd0533022ad6daa1ff42de51885f9e8bab2306c6ef1181902d1cd7709006eba1ab0587842b724e0519f295c24f6d848907f772ae9a0953fc931f4af16a07df450fb8bfa94572562437056613647818c238a6ff3f606cffa0533e4b8755da33418dfbc64a85110b1a036623c947400a536bb8df65e5ebe46f2dfd0cfc86e7aeeddd7574c253e8fbf755562b3669525d902818100f9fff30c6677b78dd31ec7a634361438457e80be7a7faf390903067ea8355faa78a1204a82b6e99cb7d9058d23c1ecf6cfe4a900137a00cecc0113fd68c5931602980267ea9a95d182d48ba0a6b4d5dd32fdac685cb2e5d8b42509b2eb59c9579ea6a67ccc7547427e2bd1fb1f23b0ccb4dd6ba7d206c8dd93253d70a451701302818100f5530dfef678d73ce6a401ae47043af10a2e3f224c71ae933035ecd68ccbc4df52d72bc6ca2b17e8faf3e548b483a2506c0369ab80df3b137b54d53fac98f95547c2bc245b416e650ce617e0d29db36066f1335a9ba02ad3e0edf9dc3d58fd835835042663edebce81803972696c789012847cb1f854ab2ac0a1bd3867ac7fb502818029c53010d456105f2bf52a9a8482bca2224a5eac74bf3cc1a4d5d291fafcdffd15a6a6448cce8efdd661f6617ca5fc37c8c885cc3374e109ac6049bcbf72b37eabf44602a2da2d4a1237fd145c863e6d75059976de762d9d258c42b0984e2a2befa01c95217c3ee9c736ff209c355466ff99375194eff943bc402ea1d172a1ed02818027175bf493bbbfb8719c12b47d967bf9eac061c90a5b5711172e9095c38bb8cc493c063abffe4bea110b0a2f22ac9311b3947ba31b7ef6bfecf8209eebd6d86c316a2366bbafda7279b2b47d5bb24b6202254f249205dcad347b574433f6593733b806f84316276c1990a016ce1bbdbe5f650325acc7791aefe515ecc60063bd02818100b6a2077f4adcf15a17092d9c4a346d6022ac48f3861b73cf714f84c440a07419a7ce75a73b9cbff4597c53c128bf81e87b272d70428a272d99f90cd9b9ea1033298e108f919c6477400145a102df3fb5601ffc4588203cf710002517bfa24e6ad32f4d09c6b1a995fa28a3104131bedd9072f3b4fb4a5c2056232643d310453f").unwrap(); - Keypair::rsa_from_pkcs8(&mut rsa_key).unwrap() - }; - TestKeypair(keypair) - } - - #[cfg(not(feature = "rsa"))] - fn arbitrary(_g: &mut Gen) -> Self { - // Small enough to be inlined. - TestKeypair(Keypair::generate_ed25519()) - } - } - - impl std::fmt::Debug for TestKeypair { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TestKeypair") - .field("public", &self.0.public()) - .finish() - } - } - - #[test] - /// Test that RPC messages can be encoded and decoded successfully. - fn encode_decode() { - fn prop(message: Message) { - let message = message.0; - - let rpc = crate::types::Rpc { - messages: vec![message.clone()], - subscriptions: vec![], - control_msgs: vec![], - }; - - let mut codec = GossipsubCodec::new(u32::MAX as usize, ValidationMode::Strict); - let mut buf = BytesMut::new(); - codec.encode(rpc.into_protobuf(), &mut buf).unwrap(); - let decoded_rpc = codec.decode(&mut buf).unwrap().unwrap(); - // mark as validated as its a published message - match decoded_rpc { - HandlerEvent::Message { mut rpc, .. } => { - rpc.messages[0].validated = true; - - assert_eq!(vec![message], rpc.messages); - } - _ => panic!("Must decode a message"), - } - } - - QuickCheck::new().quickcheck(prop as fn(_) -> _) - } - - #[test] - fn support_floodsub_with_custom_protocol() { - let protocol_config = ConfigBuilder::default() - .protocol_id("/foosub", Version::V1_1) - .support_floodsub() - .build() - .unwrap() - .protocol_config(); - - assert_eq!(protocol_config.protocol_ids[0].protocol, "/foosub"); - assert_eq!(protocol_config.protocol_ids[1].protocol, "/floodsub/1.0.0"); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs b/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs deleted file mode 100644 index f653779ba2..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -pub(crate) mod proto { - #![allow(unreachable_pub)] - include!("generated/mod.rs"); - pub use self::gossipsub::pb::{mod_RPC::SubOpts, *}; -} - -#[cfg(test)] -mod test { - use crate::rpc_proto::proto::compat; - use crate::IdentTopic as Topic; - use libp2p::identity::PeerId; - use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; - use rand::Rng; - - #[test] - fn test_multi_topic_message_compatibility() { - let topic1 = Topic::new("t1").hash(); - let topic2 = Topic::new("t2").hash(); - - let new_message1 = super::proto::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic: topic1.clone().into_string(), - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - let old_message1 = compat::pb::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic_ids: vec![topic1.clone().into_string()], - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - let old_message2 = compat::pb::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic_ids: vec![topic1.clone().into_string(), topic2.clone().into_string()], - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - - let mut new_message1b = Vec::with_capacity(new_message1.get_size()); - let mut writer = Writer::new(&mut new_message1b); - new_message1.write_message(&mut writer).unwrap(); - - let mut old_message1b = Vec::with_capacity(old_message1.get_size()); - let mut writer = Writer::new(&mut old_message1b); - old_message1.write_message(&mut writer).unwrap(); - - let mut old_message2b = Vec::with_capacity(old_message2.get_size()); - let mut writer = Writer::new(&mut old_message2b); - old_message2.write_message(&mut writer).unwrap(); - - let mut reader = BytesReader::from_bytes(&old_message1b[..]); - let new_message = - super::proto::Message::from_reader(&mut reader, &old_message1b[..]).unwrap(); - assert_eq!(new_message.topic, topic1.clone().into_string()); - - let mut reader = BytesReader::from_bytes(&old_message2b[..]); - let new_message = - super::proto::Message::from_reader(&mut reader, &old_message2b[..]).unwrap(); - assert_eq!(new_message.topic, topic2.into_string()); - - let mut reader = BytesReader::from_bytes(&new_message1b[..]); - let old_message = - compat::pb::Message::from_reader(&mut reader, &new_message1b[..]).unwrap(); - assert_eq!(old_message.topic_ids, vec![topic1.into_string()]); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs b/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs deleted file mode 100644 index 02bb9b4eab..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::types::Subscription; -use crate::TopicHash; -use std::collections::{BTreeSet, HashMap, HashSet}; - -pub trait TopicSubscriptionFilter { - /// Returns true iff the topic is of interest and we can subscribe to it. - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool; - - /// Filters a list of incoming subscriptions and returns a filtered set - /// By default this deduplicates the subscriptions and calls - /// [`Self::filter_incoming_subscription_set`] on the filtered set. - fn filter_incoming_subscriptions<'a>( - &mut self, - subscriptions: &'a [Subscription], - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - let mut filtered_subscriptions: HashMap = HashMap::new(); - for subscription in subscriptions { - use std::collections::hash_map::Entry::*; - match filtered_subscriptions.entry(subscription.topic_hash.clone()) { - Occupied(entry) => { - if entry.get().action != subscription.action { - entry.remove(); - } - } - Vacant(entry) => { - entry.insert(subscription); - } - } - } - self.filter_incoming_subscription_set( - filtered_subscriptions.into_values().collect(), - currently_subscribed_topics, - ) - } - - /// Filters a set of deduplicated subscriptions - /// By default this filters the elements based on [`Self::allow_incoming_subscription`]. - fn filter_incoming_subscription_set<'a>( - &mut self, - mut subscriptions: HashSet<&'a Subscription>, - _currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - subscriptions.retain(|s| { - if self.allow_incoming_subscription(s) { - true - } else { - tracing::debug!(subscription=?s, "Filtered incoming subscription"); - false - } - }); - Ok(subscriptions) - } - - /// Returns true iff we allow an incoming subscription. - /// This is used by the default implementation of filter_incoming_subscription_set to decide - /// whether to filter out a subscription or not. - /// By default this uses can_subscribe to decide the same for incoming subscriptions as for - /// outgoing ones. - fn allow_incoming_subscription(&mut self, subscription: &Subscription) -> bool { - self.can_subscribe(&subscription.topic_hash) - } -} - -//some useful implementers - -/// Allows all subscriptions -#[derive(Default, Clone)] -pub struct AllowAllSubscriptionFilter {} - -impl TopicSubscriptionFilter for AllowAllSubscriptionFilter { - fn can_subscribe(&mut self, _: &TopicHash) -> bool { - true - } -} - -/// Allows only whitelisted subscriptions -#[derive(Default, Clone)] -pub struct WhitelistSubscriptionFilter(pub HashSet); - -impl TopicSubscriptionFilter for WhitelistSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.0.contains(topic_hash) - } -} - -/// Adds a max count to a given subscription filter -pub struct MaxCountSubscriptionFilter { - pub filter: T, - pub max_subscribed_topics: usize, - pub max_subscriptions_per_request: usize, -} - -impl TopicSubscriptionFilter for MaxCountSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.filter.can_subscribe(topic_hash) - } - - fn filter_incoming_subscriptions<'a>( - &mut self, - subscriptions: &'a [Subscription], - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - if subscriptions.len() > self.max_subscriptions_per_request { - return Err("too many subscriptions per request".into()); - } - let result = self - .filter - .filter_incoming_subscriptions(subscriptions, currently_subscribed_topics)?; - - use crate::types::SubscriptionAction::*; - - let mut unsubscribed = 0; - let mut new_subscribed = 0; - for s in &result { - let currently_contained = currently_subscribed_topics.contains(&s.topic_hash); - match s.action { - Unsubscribe => { - if currently_contained { - unsubscribed += 1; - } - } - Subscribe => { - if !currently_contained { - new_subscribed += 1; - } - } - } - } - - if new_subscribed + currently_subscribed_topics.len() - > self.max_subscribed_topics + unsubscribed - { - return Err("too many subscribed topics".into()); - } - - Ok(result) - } -} - -/// Combines two subscription filters -pub struct CombinedSubscriptionFilters { - pub filter1: T, - pub filter2: S, -} - -impl TopicSubscriptionFilter for CombinedSubscriptionFilters -where - T: TopicSubscriptionFilter, - S: TopicSubscriptionFilter, -{ - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.filter1.can_subscribe(topic_hash) && self.filter2.can_subscribe(topic_hash) - } - - fn filter_incoming_subscription_set<'a>( - &mut self, - subscriptions: HashSet<&'a Subscription>, - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - let intermediate = self - .filter1 - .filter_incoming_subscription_set(subscriptions, currently_subscribed_topics)?; - self.filter2 - .filter_incoming_subscription_set(intermediate, currently_subscribed_topics) - } -} - -pub struct CallbackSubscriptionFilter(pub T) -where - T: FnMut(&TopicHash) -> bool; - -impl TopicSubscriptionFilter for CallbackSubscriptionFilter -where - T: FnMut(&TopicHash) -> bool, -{ - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - (self.0)(topic_hash) - } -} - -///A subscription filter that filters topics based on a regular expression. -pub struct RegexSubscriptionFilter(pub regex::Regex); - -impl TopicSubscriptionFilter for RegexSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.0.is_match(topic_hash.as_str()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::types::SubscriptionAction::*; - - #[test] - fn test_filter_incoming_allow_all_with_duplicates() { - let mut filter = AllowAllSubscriptionFilter {}; - - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let old = BTreeSet::from_iter(vec![t1.clone()]); - let subscriptions = vec![ - Subscription { - action: Unsubscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t2.clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - Subscription { - action: Subscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t1, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[4]].into_iter().collect()); - } - - #[test] - fn test_filter_incoming_whitelist() { - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let mut filter = WhitelistSubscriptionFilter(HashSet::from_iter(vec![t1.clone()])); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[0]].into_iter().collect()); - } - - #[test] - fn test_filter_incoming_too_many_subscriptions_per_request() { - let t1 = TopicHash::from_raw("t1"); - - let mut filter = MaxCountSubscriptionFilter { - filter: AllowAllSubscriptionFilter {}, - max_subscribed_topics: 100, - max_subscriptions_per_request: 2, - }; - - let old = Default::default(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t1, - }, - ]; - - let result = filter.filter_incoming_subscriptions(&subscriptions, &old); - assert_eq!(result, Err("too many subscriptions per request".into())); - } - - #[test] - fn test_filter_incoming_too_many_subscriptions() { - let t: Vec<_> = (0..4) - .map(|i| TopicHash::from_raw(format!("t{i}"))) - .collect(); - - let mut filter = MaxCountSubscriptionFilter { - filter: AllowAllSubscriptionFilter {}, - max_subscribed_topics: 3, - max_subscriptions_per_request: 2, - }; - - let old = t[0..2].iter().cloned().collect(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t[2].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[3].clone(), - }, - ]; - - let result = filter.filter_incoming_subscriptions(&subscriptions, &old); - assert_eq!(result, Err("too many subscribed topics".into())); - } - - #[test] - fn test_filter_incoming_max_subscribed_valid() { - let t: Vec<_> = (0..5) - .map(|i| TopicHash::from_raw(format!("t{i}"))) - .collect(); - - let mut filter = MaxCountSubscriptionFilter { - filter: WhitelistSubscriptionFilter(t.iter().take(4).cloned().collect()), - max_subscribed_topics: 2, - max_subscriptions_per_request: 5, - }; - - let old = t[0..2].iter().cloned().collect(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t[4].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[2].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[3].clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t[0].clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t[1].clone(), - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, subscriptions[1..].iter().collect()); - } - - #[test] - fn test_callback_filter() { - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let mut filter = CallbackSubscriptionFilter(|h| h.as_str() == "t1"); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[0]].into_iter().collect()); - } - - #[test] - fn test_regex_subscription_filter() { - let t1 = TopicHash::from_raw("tt"); - let t2 = TopicHash::from_raw("et3t3te"); - let t3 = TopicHash::from_raw("abcdefghijklmnopqrsuvwxyz"); - - let mut filter = RegexSubscriptionFilter(regex::Regex::new("t.*t").unwrap()); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - Subscription { - action: Subscribe, - topic_hash: t3, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, subscriptions[..2].iter().collect()); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs b/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs deleted file mode 100644 index a3e5c01ac4..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This implements a time-based LRU cache for checking gossipsub message duplicates. - -use fnv::FnvHashMap; -use std::collections::hash_map::{ - self, - Entry::{Occupied, Vacant}, -}; -use std::collections::VecDeque; -use std::time::Duration; -use web_time::Instant; - -struct ExpiringElement { - /// The element that expires - element: Element, - /// The expire time. - expires: Instant, -} - -pub(crate) struct TimeCache { - /// Mapping a key to its value together with its latest expire time (can be updated through - /// reinserts). - map: FnvHashMap>, - /// An ordered list of keys by expires time. - list: VecDeque>, - /// The time elements remain in the cache. - ttl: Duration, -} - -pub(crate) struct OccupiedEntry<'a, K, V> { - entry: hash_map::OccupiedEntry<'a, K, ExpiringElement>, -} - -impl<'a, K, V> OccupiedEntry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn into_mut(self) -> &'a mut V { - &mut self.entry.into_mut().element - } -} - -pub(crate) struct VacantEntry<'a, K, V> { - expiration: Instant, - entry: hash_map::VacantEntry<'a, K, ExpiringElement>, - list: &'a mut VecDeque>, -} - -impl<'a, K, V> VacantEntry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn insert(self, value: V) -> &'a mut V { - self.list.push_back(ExpiringElement { - element: self.entry.key().clone(), - expires: self.expiration, - }); - &mut self - .entry - .insert(ExpiringElement { - element: value, - expires: self.expiration, - }) - .element - } -} - -pub(crate) enum Entry<'a, K: 'a, V: 'a> { - Occupied(OccupiedEntry<'a, K, V>), - Vacant(VacantEntry<'a, K, V>), -} - -impl<'a, K: 'a, V: 'a> Entry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn or_default(self) -> &'a mut V - where - V: Default, - { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(V::default()), - } - } -} - -impl TimeCache -where - Key: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn new(ttl: Duration) -> Self { - TimeCache { - map: FnvHashMap::default(), - list: VecDeque::new(), - ttl, - } - } - - fn remove_expired_keys(&mut self, now: Instant) { - while let Some(element) = self.list.pop_front() { - if element.expires > now { - self.list.push_front(element); - break; - } - if let Occupied(entry) = self.map.entry(element.element.clone()) { - if entry.get().expires <= now { - entry.remove(); - } - } - } - } - - pub(crate) fn entry(&mut self, key: Key) -> Entry { - let now = Instant::now(); - self.remove_expired_keys(now); - match self.map.entry(key) { - Occupied(entry) => Entry::Occupied(OccupiedEntry { entry }), - Vacant(entry) => Entry::Vacant(VacantEntry { - expiration: now + self.ttl, - entry, - list: &mut self.list, - }), - } - } - - /// Empties the entire cache. - #[cfg(test)] - pub(crate) fn clear(&mut self) { - self.map.clear(); - self.list.clear(); - } - - pub(crate) fn contains_key(&self, key: &Key) -> bool { - self.map.contains_key(key) - } -} - -pub(crate) struct DuplicateCache(TimeCache); - -impl DuplicateCache -where - Key: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn new(ttl: Duration) -> Self { - Self(TimeCache::new(ttl)) - } - - // Inserts new elements and removes any expired elements. - // - // If the key was not present this returns `true`. If the value was already present this - // returns `false`. - pub(crate) fn insert(&mut self, key: Key) -> bool { - if let Entry::Vacant(entry) = self.0.entry(key) { - entry.insert(()); - true - } else { - false - } - } - - pub(crate) fn contains(&self, key: &Key) -> bool { - self.0.contains_key(key) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn cache_added_entries_exist() { - let mut cache = DuplicateCache::new(Duration::from_secs(10)); - - cache.insert("t"); - cache.insert("e"); - - // Should report that 't' and 't' already exists - assert!(!cache.insert("t")); - assert!(!cache.insert("e")); - } - - #[test] - fn cache_entries_expire() { - let mut cache = DuplicateCache::new(Duration::from_millis(100)); - - cache.insert("t"); - assert!(!cache.insert("t")); - cache.insert("e"); - //assert!(!cache.insert("t")); - assert!(!cache.insert("e")); - // sleep until cache expiry - std::thread::sleep(Duration::from_millis(101)); - // add another element to clear previous cache - cache.insert("s"); - - // should be removed from the cache - assert!(cache.insert("t")); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/topic.rs b/beacon_node/lighthouse_network/gossipsub/src/topic.rs deleted file mode 100644 index a73496b53f..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/topic.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto::proto; -use base64::prelude::*; -use prometheus_client::encoding::EncodeLabelSet; -use quick_protobuf::Writer; -use sha2::{Digest, Sha256}; -use std::fmt; - -/// A generic trait that can be extended for various hashing types for a topic. -pub trait Hasher { - /// The function that takes a topic string and creates a topic hash. - fn hash(topic_string: String) -> TopicHash; -} - -/// A type for representing topics who use the identity hash. -#[derive(Debug, Clone)] -pub struct IdentityHash {} -impl Hasher for IdentityHash { - /// Creates a [`TopicHash`] as a raw string. - fn hash(topic_string: String) -> TopicHash { - TopicHash { hash: topic_string } - } -} - -#[derive(Debug, Clone)] -pub struct Sha256Hash {} -impl Hasher for Sha256Hash { - /// Creates a [`TopicHash`] by SHA256 hashing the topic then base64 encoding the - /// hash. - fn hash(topic_string: String) -> TopicHash { - use quick_protobuf::MessageWrite; - - let topic_descripter = proto::TopicDescriptor { - name: Some(topic_string), - auth: None, - enc: None, - }; - let mut bytes = Vec::with_capacity(topic_descripter.get_size()); - let mut writer = Writer::new(&mut bytes); - topic_descripter - .write_message(&mut writer) - .expect("Encoding to succeed"); - let hash = BASE64_STANDARD.encode(Sha256::digest(&bytes)); - TopicHash { hash } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, EncodeLabelSet)] -pub struct TopicHash { - /// The topic hash. Stored as a string to align with the protobuf API. - hash: String, -} - -impl TopicHash { - pub fn from_raw(hash: impl Into) -> TopicHash { - TopicHash { hash: hash.into() } - } - - pub fn into_string(self) -> String { - self.hash - } - - pub fn as_str(&self) -> &str { - &self.hash - } -} - -/// A gossipsub topic. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Topic { - topic: String, - phantom_data: std::marker::PhantomData, -} - -impl From> for TopicHash { - fn from(topic: Topic) -> TopicHash { - topic.hash() - } -} - -impl Topic { - pub fn new(topic: impl Into) -> Self { - Topic { - topic: topic.into(), - phantom_data: std::marker::PhantomData, - } - } - - pub fn hash(&self) -> TopicHash { - H::hash(self.topic.clone()) - } -} - -impl fmt::Display for Topic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.topic) - } -} - -impl fmt::Display for TopicHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.hash) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/transform.rs b/beacon_node/lighthouse_network/gossipsub/src/transform.rs deleted file mode 100644 index 6f57d9fc46..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/transform.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This trait allows of extended user-level decoding that can apply to message-data before a -//! message-id is calculated. -//! -//! This is primarily designed to allow applications to implement their own custom compression -//! algorithms that can be topic-specific. Once the raw data is transformed the message-id is then -//! calculated, allowing for applications to employ message-id functions post compression. - -use crate::{Message, RawMessage, TopicHash}; - -/// A general trait of transforming a [`RawMessage`] into a [`Message`]. The -/// [`RawMessage`] is obtained from the wire and the [`Message`] is used to -/// calculate the [`crate::MessageId`] of the message and is what is sent to the application. -/// -/// The inbound/outbound transforms must be inverses. Applying the inbound transform and then the -/// outbound transform MUST leave the underlying data un-modified. -/// -/// By default, this is the identity transform for all fields in [`Message`]. -pub trait DataTransform { - /// Takes a [`RawMessage`] received and converts it to a [`Message`]. - fn inbound_transform(&self, raw_message: RawMessage) -> Result; - - /// Takes the data to be published (a topic and associated data) transforms the data. The - /// transformed data will then be used to create a [`crate::RawMessage`] to be sent to peers. - fn outbound_transform( - &self, - topic: &TopicHash, - data: Vec, - ) -> Result, std::io::Error>; -} - -/// The default transform, the raw data is propagated as is to the application layer gossipsub. -#[derive(Default, Clone)] -pub struct IdentityTransform; - -impl DataTransform for IdentityTransform { - fn inbound_transform(&self, raw_message: RawMessage) -> Result { - Ok(Message { - source: raw_message.source, - data: raw_message.data, - sequence_number: raw_message.sequence_number, - topic: raw_message.topic, - }) - } - - fn outbound_transform( - &self, - _topic: &TopicHash, - data: Vec, - ) -> Result, std::io::Error> { - Ok(data) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/types.rs b/beacon_node/lighthouse_network/gossipsub/src/types.rs deleted file mode 100644 index f5dac380e3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/types.rs +++ /dev/null @@ -1,882 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! A collection of types using the Gossipsub system. -use crate::metrics::Metrics; -use crate::TopicHash; -use async_channel::{Receiver, Sender}; -use futures::stream::Peekable; -use futures::{Future, Stream, StreamExt}; -use futures_timer::Delay; -use hashlink::LinkedHashMap; -use libp2p::identity::PeerId; -use libp2p::swarm::ConnectionId; -use prometheus_client::encoding::EncodeLabelValue; -use quick_protobuf::MessageWrite; -use std::collections::BTreeSet; -use std::fmt::Debug; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Instant; -use std::{fmt, pin::Pin}; -use web_time::Duration; - -use crate::rpc_proto::proto; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of messages that have expired while attempting to send to a peer. -#[derive(Clone, Debug, Default)] -pub struct FailedMessages { - /// The number of publish messages that failed to be published in a heartbeat. - pub publish: usize, - /// The number of forward messages that failed to be published in a heartbeat. - pub forward: usize, - /// The number of messages that were failed to be sent to the priority queue as it was full. - pub priority: usize, - /// The number of messages that were failed to be sent to the non-priority queue as it was full. - pub non_priority: usize, -} - -impl FailedMessages { - /// The total number of messages that expired due a timeout. - pub fn total_timeout(&self) -> usize { - self.publish + self.forward - } - - /// The total number of messages that failed due to the queue being full. - pub fn total_queue_full(&self) -> usize { - self.priority + self.non_priority - } - - /// The total failed messages in a heartbeat. - pub fn total(&self) -> usize { - self.total_timeout() + self.total_queue_full() - } -} - -#[derive(Debug)] -/// Validation kinds from the application for received messages. -pub enum MessageAcceptance { - /// The message is considered valid, and it should be delivered and forwarded to the network. - Accept, - /// The message is considered invalid, and it should be rejected and trigger the Pâ‚„ penalty. - Reject, - /// The message is neither delivered nor forwarded to the network, but the router does not - /// trigger the Pâ‚„ penalty. - Ignore, -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct MessageId(pub Vec); - -impl MessageId { - pub fn new(value: &[u8]) -> Self { - Self(value.to_vec()) - } -} - -impl>> From for MessageId { - fn from(value: T) -> Self { - Self(value.into()) - } -} - -impl std::fmt::Display for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex_fmt::HexFmt(&self.0)) - } -} - -impl std::fmt::Debug for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "MessageId({})", hex_fmt::HexFmt(&self.0)) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct PeerConnections { - /// The kind of protocol the peer supports. - pub(crate) kind: PeerKind, - /// Its current connections. - pub(crate) connections: Vec, - /// The rpc sender to the peer. - pub(crate) sender: RpcSender, - /// Subscribed topics. - pub(crate) topics: BTreeSet, - /// IDONTWANT messages received from the peer. - pub(crate) dont_send_received: LinkedHashMap, - /// IDONTWANT messages we sent to the peer. - pub(crate) dont_send_sent: LinkedHashMap, -} - -/// Describes the types of peers that can exist in the gossipsub context. -#[derive(Debug, Clone, PartialEq, Hash, EncodeLabelValue, Eq)] -#[allow(non_camel_case_types)] -pub enum PeerKind { - /// A gossipsub 1.2 peer. - Gossipsubv1_2, - /// A gossipsub 1.1 peer. - Gossipsubv1_1, - /// A gossipsub 1.0 peer. - Gossipsub, - /// A floodsub peer. - Floodsub, - /// The peer doesn't support any of the protocols. - NotSupported, -} - -impl PeerKind { - /// Returns true if peer speaks any gossipsub version. - pub(crate) fn is_gossipsub(&self) -> bool { - matches!( - self, - Self::Gossipsubv1_2 | Self::Gossipsubv1_1 | Self::Gossipsub - ) - } -} - -/// A message received by the gossipsub system and stored locally in caches.. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub struct RawMessage { - /// Id of the peer that published this message. - pub source: Option, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: Option, - - /// The topic this message belongs to - pub topic: TopicHash, - - /// The signature of the message if it's signed. - pub signature: Option>, - - /// The public key of the message if it is signed and the source [`PeerId`] cannot be inlined. - pub key: Option>, - - /// Flag indicating if this message has been validated by the application or not. - pub validated: bool, -} - -impl RawMessage { - /// Calculates the encoded length of this message (used for calculating metrics). - pub fn raw_protobuf_len(&self) -> usize { - let message = proto::Message { - from: self.source.map(|m| m.to_bytes()), - data: Some(self.data.clone()), - seqno: self.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(self.topic.clone()), - signature: self.signature.clone(), - key: self.key.clone(), - }; - message.get_size() - } -} - -impl From for proto::Message { - fn from(raw: RawMessage) -> Self { - proto::Message { - from: raw.source.map(|m| m.to_bytes()), - data: Some(raw.data), - seqno: raw.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(raw.topic), - signature: raw.signature, - key: raw.key, - } - } -} - -/// The message sent to the user after a [`RawMessage`] has been transformed by a -/// [`crate::DataTransform`]. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Message { - /// Id of the peer that published this message. - pub source: Option, - - /// Content of the message. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: Option, - - /// The topic this message belongs to - pub topic: TopicHash, -} - -impl fmt::Debug for Message { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Message") - .field( - "data", - &format_args!("{:<20}", &hex_fmt::HexFmt(&self.data)), - ) - .field("source", &self.source) - .field("sequence_number", &self.sequence_number) - .field("topic", &self.topic) - .finish() - } -} - -/// A subscription received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Subscription { - /// Action to perform. - pub action: SubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic_hash: TopicHash, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PeerInfo { - pub(crate) peer_id: Option, - //TODO add this when RFC: Signed Address Records got added to the spec (see pull request - // https://github.com/libp2p/specs/pull/217) - //pub signed_peer_record: ?, -} - -/// A Control message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ControlAction { - /// Node broadcasts known messages per topic - IHave control message. - IHave(IHave), - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant(IWant), - /// The node has been added to the mesh - Graft control message. - Graft(Graft), - /// The node has been removed from the mesh - Prune control message. - Prune(Prune), - /// The node requests us to not forward message ids (peer_id + sequence _number) - IDontWant control message. - IDontWant(IDontWant), -} - -/// Node broadcasts known messages per topic - IHave control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IHave { - /// The topic of the messages. - pub(crate) topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - pub(crate) message_ids: Vec, -} - -/// The node requests specific message ids (peer_id + sequence _number) - IWant control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - pub(crate) message_ids: Vec, -} - -/// The node has been added to the mesh - Graft control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Graft { - /// The mesh topic the peer should be added to. - pub(crate) topic_hash: TopicHash, -} - -/// The node has been removed from the mesh - Prune control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Prune { - /// The mesh topic the peer should be removed from. - pub(crate) topic_hash: TopicHash, - /// A list of peers to be proposed to the removed peer as peer exchange - pub(crate) peers: Vec, - /// The backoff time in seconds before we allow to reconnect - pub(crate) backoff: Option, -} - -/// The node requests us to not forward message ids - IDontWant control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IDontWant { - /// A list of known message ids. - pub(crate) message_ids: Vec, -} - -/// A Gossipsub RPC message sent. -#[derive(Debug)] -pub enum RpcOut { - /// Publish a Gossipsub message on network. The [`Delay`] tags the time we attempted to - /// send it. - Publish { message: RawMessage, timeout: Delay }, - /// Forward a Gossipsub message to the network. The [`Delay`] tags the time we attempted to - /// send it. - Forward { message: RawMessage, timeout: Delay }, - /// Subscribe a topic. - Subscribe(TopicHash), - /// Unsubscribe a topic. - Unsubscribe(TopicHash), - /// Send a GRAFT control message. - Graft(Graft), - /// Send a PRUNE control message. - Prune(Prune), - /// Send a IHave control message. - IHave(IHave), - /// Send a IWant control message. - IWant(IWant), - /// Send a IDontWant control message. - IDontWant(IDontWant), -} - -impl RpcOut { - /// Converts the GossipsubRPC into its protobuf format. - // A convenience function to avoid explicitly specifying types. - pub fn into_protobuf(self) -> proto::RPC { - self.into() - } -} - -impl From for proto::RPC { - /// Converts the RPC into protobuf format. - fn from(rpc: RpcOut) -> Self { - match rpc { - RpcOut::Publish { - message, - timeout: _, - } => proto::RPC { - subscriptions: Vec::new(), - publish: vec![message.into()], - control: None, - }, - RpcOut::Forward { - message, - timeout: _, - } => proto::RPC { - publish: vec![message.into()], - subscriptions: Vec::new(), - control: None, - }, - RpcOut::Subscribe(topic) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![proto::SubOpts { - subscribe: Some(true), - topic_id: Some(topic.into_string()), - }], - control: None, - }, - RpcOut::Unsubscribe(topic) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![proto::SubOpts { - subscribe: Some(false), - topic_id: Some(topic.into_string()), - }], - control: None, - }, - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - iwant: vec![], - graft: vec![], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::IWant(IWant { message_ids }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - graft: vec![], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::Graft(Graft { topic_hash }) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![], - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - proto::RPC { - publish: Vec::new(), - subscriptions: vec![], - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![], - prune: vec![proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - peers: peers - .into_iter() - .map(|info| proto::PeerInfo { - peer_id: info.peer_id.map(|id| id.to_bytes()), - // TODO, see https://github.com/libp2p/specs/pull/217 - signed_peer_record: None, - }) - .collect(), - backoff, - }], - idontwant: vec![], - }), - } - } - RpcOut::IDontWant(IDontWant { message_ids }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![], - prune: vec![], - idontwant: vec![proto::ControlIDontWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - }), - }, - } - } -} - -/// An RPC received/sent. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Rpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, - /// List of Gossipsub control messages. - pub control_msgs: Vec, -} - -impl Rpc { - /// Converts the GossipsubRPC into its protobuf format. - // A convenience function to avoid explicitly specifying types. - pub fn into_protobuf(self) -> proto::RPC { - self.into() - } -} - -impl From for proto::RPC { - /// Converts the RPC into protobuf format. - fn from(rpc: Rpc) -> Self { - // Messages - let mut publish = Vec::new(); - - for message in rpc.messages.into_iter() { - let message = proto::Message { - from: message.source.map(|m| m.to_bytes()), - data: Some(message.data), - seqno: message.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(message.topic), - signature: message.signature, - key: message.key, - }; - - publish.push(message); - } - - // subscriptions - let subscriptions = rpc - .subscriptions - .into_iter() - .map(|sub| proto::SubOpts { - subscribe: Some(sub.action == SubscriptionAction::Subscribe), - topic_id: Some(sub.topic_hash.into_string()), - }) - .collect::>(); - - // control messages - let mut control = proto::ControlMessage { - ihave: Vec::new(), - iwant: Vec::new(), - graft: Vec::new(), - prune: Vec::new(), - idontwant: Vec::new(), - }; - - let empty_control_msg = rpc.control_msgs.is_empty(); - - for action in rpc.control_msgs { - match action { - // collect all ihave messages - ControlAction::IHave(IHave { - topic_hash, - message_ids, - }) => { - let rpc_ihave = proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.ihave.push(rpc_ihave); - } - ControlAction::IWant(IWant { message_ids }) => { - let rpc_iwant = proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.iwant.push(rpc_iwant); - } - ControlAction::Graft(Graft { topic_hash }) => { - let rpc_graft = proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }; - control.graft.push(rpc_graft); - } - ControlAction::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - let rpc_prune = proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - peers: peers - .into_iter() - .map(|info| proto::PeerInfo { - peer_id: info.peer_id.map(|id| id.to_bytes()), - // TODO, see https://github.com/libp2p/specs/pull/217 - signed_peer_record: None, - }) - .collect(), - backoff, - }; - control.prune.push(rpc_prune); - } - ControlAction::IDontWant(IDontWant { message_ids }) => { - let rpc_idontwant = proto::ControlIDontWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.idontwant.push(rpc_idontwant); - } - } - } - - proto::RPC { - subscriptions, - publish, - control: if empty_control_msg { - None - } else { - Some(control) - }, - } - } -} - -impl fmt::Debug for Rpc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut b = f.debug_struct("GossipsubRpc"); - if !self.messages.is_empty() { - b.field("messages", &self.messages); - } - if !self.subscriptions.is_empty() { - b.field("subscriptions", &self.subscriptions); - } - if !self.control_msgs.is_empty() { - b.field("control_msgs", &self.control_msgs); - } - b.finish() - } -} - -impl PeerKind { - pub fn as_static_ref(&self) -> &'static str { - match self { - Self::NotSupported => "Not Supported", - Self::Floodsub => "Floodsub", - Self::Gossipsub => "Gossipsub v1.0", - Self::Gossipsubv1_1 => "Gossipsub v1.1", - Self::Gossipsubv1_2 => "Gossipsub v1.2", - } - } -} - -impl AsRef for PeerKind { - fn as_ref(&self) -> &str { - self.as_static_ref() - } -} - -impl fmt::Display for PeerKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_ref()) - } -} - -/// `RpcOut` sender that is priority aware. -#[derive(Debug, Clone)] -pub(crate) struct RpcSender { - cap: usize, - len: Arc, - pub(crate) priority_sender: Sender, - pub(crate) non_priority_sender: Sender, - priority_receiver: Receiver, - non_priority_receiver: Receiver, -} - -impl RpcSender { - /// Create a RpcSender. - pub(crate) fn new(cap: usize) -> RpcSender { - let (priority_sender, priority_receiver) = async_channel::unbounded(); - let (non_priority_sender, non_priority_receiver) = async_channel::bounded(cap / 2); - let len = Arc::new(AtomicUsize::new(0)); - RpcSender { - cap: cap / 2, - len, - priority_sender, - non_priority_sender, - priority_receiver, - non_priority_receiver, - } - } - - /// Create a new Receiver to the sender. - pub(crate) fn new_receiver(&self) -> RpcReceiver { - RpcReceiver { - priority_len: self.len.clone(), - priority: self.priority_receiver.clone().peekable(), - non_priority: self.non_priority_receiver.clone().peekable(), - } - } - - /// Send a `RpcOut::Graft` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn graft(&mut self, graft: Graft) { - self.priority_sender - .try_send(RpcOut::Graft(graft)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Prune` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn prune(&mut self, prune: Prune) { - self.priority_sender - .try_send(RpcOut::Prune(prune)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::IHave` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn ihave(&mut self, ihave: IHave) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IHave(ihave)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::IHave` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn iwant(&mut self, iwant: IWant) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IWant(iwant)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::IWant` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn idontwant(&mut self, idontwant: IDontWant) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IDontWant(idontwant)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::Subscribe` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn subscribe(&mut self, topic: TopicHash) { - self.priority_sender - .try_send(RpcOut::Subscribe(topic)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Unsubscribe` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn unsubscribe(&mut self, topic: TopicHash) { - self.priority_sender - .try_send(RpcOut::Unsubscribe(topic)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Publish` message to the `RpcReceiver` - /// this is high priority. If message sending fails, an `Err` is returned. - pub(crate) fn publish( - &mut self, - message: RawMessage, - timeout: Duration, - metrics: Option<&mut Metrics>, - ) -> Result<(), ()> { - if self.len.load(Ordering::Relaxed) >= self.cap { - return Err(()); - } - self.priority_sender - .try_send(RpcOut::Publish { - message: message.clone(), - timeout: Delay::new(timeout), - }) - .expect("Channel is unbounded and should always be open"); - self.len.fetch_add(1, Ordering::Relaxed); - - if let Some(m) = metrics { - m.msg_sent(&message.topic, message.raw_protobuf_len()); - } - - Ok(()) - } - - /// Send a `RpcOut::Forward` message to the `RpcReceiver` - /// this is high priority. If the queue is full the message is discarded. - pub(crate) fn forward( - &mut self, - message: RawMessage, - timeout: Duration, - metrics: Option<&mut Metrics>, - ) -> Result<(), ()> { - self.non_priority_sender - .try_send(RpcOut::Forward { - message: message.clone(), - timeout: Delay::new(timeout), - }) - .map_err(|_| ())?; - - if let Some(m) = metrics { - m.msg_sent(&message.topic, message.raw_protobuf_len()); - } - - Ok(()) - } - - /// Returns the current size of the priority queue. - pub(crate) fn priority_len(&self) -> usize { - self.len.load(Ordering::Relaxed) - } - - /// Returns the current size of the non-priority queue. - pub(crate) fn non_priority_len(&self) -> usize { - self.non_priority_sender.len() - } -} - -/// `RpcOut` sender that is priority aware. -#[derive(Debug)] -pub struct RpcReceiver { - /// The maximum length of the priority queue. - pub(crate) priority_len: Arc, - /// The priority queue receiver. - pub(crate) priority: Peekable>, - /// The non priority queue receiver. - pub(crate) non_priority: Peekable>, -} - -impl RpcReceiver { - // Peek the next message in the queues and return it if its timeout has elapsed. - // Returns `None` if there aren't any more messages on the stream or none is stale. - pub(crate) fn poll_stale(&mut self, cx: &mut Context<'_>) -> Poll> { - // Peek priority queue. - let priority = match Pin::new(&mut self.priority).poll_peek_mut(cx) { - Poll::Ready(Some(RpcOut::Publish { - message: _, - ref mut timeout, - })) => { - if Pin::new(timeout).poll(cx).is_ready() { - // Return the message. - let dropped = futures::ready!(self.priority.poll_next_unpin(cx)) - .expect("There should be a message"); - return Poll::Ready(Some(dropped)); - } - Poll::Ready(None) - } - poll => poll, - }; - - let non_priority = match Pin::new(&mut self.non_priority).poll_peek_mut(cx) { - Poll::Ready(Some(RpcOut::Forward { - message: _, - ref mut timeout, - })) => { - if Pin::new(timeout).poll(cx).is_ready() { - // Return the message. - let dropped = futures::ready!(self.non_priority.poll_next_unpin(cx)) - .expect("There should be a message"); - return Poll::Ready(Some(dropped)); - } - Poll::Ready(None) - } - poll => poll, - }; - - match (priority, non_priority) { - (Poll::Ready(None), Poll::Ready(None)) => Poll::Ready(None), - _ => Poll::Pending, - } - } - - /// Poll queues and return true if both are empty. - pub(crate) fn poll_is_empty(&mut self, cx: &mut Context<'_>) -> bool { - matches!( - ( - Pin::new(&mut self.priority).poll_peek(cx), - Pin::new(&mut self.non_priority).poll_peek(cx), - ), - (Poll::Ready(None), Poll::Ready(None)) - ) - } -} - -impl Stream for RpcReceiver { - type Item = RpcOut; - - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - // The priority queue is first polled. - if let Poll::Ready(rpc) = Pin::new(&mut self.priority).poll_next(cx) { - if let Some(RpcOut::Publish { .. }) = rpc { - self.priority_len.fetch_sub(1, Ordering::Relaxed); - } - return Poll::Ready(rpc); - } - // Then we poll the non priority. - Pin::new(&mut self.non_priority).poll_next(cx) - } -} diff --git a/beacon_node/lighthouse_network/src/discovery/enr.rs b/beacon_node/lighthouse_network/src/discovery/enr.rs index 8067711954..e70c8047e0 100644 --- a/beacon_node/lighthouse_network/src/discovery/enr.rs +++ b/beacon_node/lighthouse_network/src/discovery/enr.rs @@ -9,13 +9,13 @@ use crate::NetworkConfig; use alloy_rlp::bytes::Bytes; use libp2p::identity::Keypair; use lighthouse_version::{client_name, version}; -use slog::{debug, warn}; use ssz::{Decode, Encode}; use ssz_types::BitVector; use std::fs::File; use std::io::prelude::*; use std::path::Path; use std::str::FromStr; +use tracing::{debug, warn}; use types::{ChainSpec, EnrForkId, EthSpec}; use super::enr_ext::{EnrExt, QUIC6_ENR_KEY, QUIC_ENR_KEY}; @@ -99,20 +99,19 @@ pub fn use_or_load_enr( enr_key: &CombinedKey, local_enr: &mut Enr, config: &NetworkConfig, - log: &slog::Logger, ) -> Result<(), String> { let enr_f = config.network_dir.join(ENR_FILENAME); if let Ok(mut enr_file) = File::open(enr_f.clone()) { let mut enr_string = String::new(); match enr_file.read_to_string(&mut enr_string) { - Err(_) => debug!(log, "Could not read ENR from file"), + Err(_) => debug!("Could not read ENR from file"), Ok(_) => { match Enr::from_str(&enr_string) { Ok(disk_enr) => { // if the same node id, then we may need to update our sequence number if local_enr.node_id() == disk_enr.node_id() { if compare_enr(local_enr, &disk_enr) { - debug!(log, "ENR loaded from disk"; "file" => ?enr_f); + debug!(file = ?enr_f,"ENR loaded from disk"); // the stored ENR has the same configuration, use it *local_enr = disk_enr; return Ok(()); @@ -125,18 +124,18 @@ pub fn use_or_load_enr( local_enr.set_seq(new_seq_no, enr_key).map_err(|e| { format!("Could not update ENR sequence number: {:?}", e) })?; - debug!(log, "ENR sequence number increased"; "seq" => new_seq_no); + debug!(seq = new_seq_no, "ENR sequence number increased"); } } Err(e) => { - warn!(log, "ENR from file could not be decoded"; "error" => ?e); + warn!(error = ?e,"ENR from file could not be decoded"); } } } } } - save_enr_to_disk(&config.network_dir, local_enr, log); + save_enr_to_disk(&config.network_dir, local_enr); Ok(()) } @@ -150,7 +149,6 @@ pub fn build_or_load_enr( local_key: Keypair, config: &NetworkConfig, enr_fork_id: &EnrForkId, - log: &slog::Logger, spec: &ChainSpec, ) -> Result { // Build the local ENR. @@ -159,7 +157,7 @@ pub fn build_or_load_enr( let enr_key = CombinedKey::from_libp2p(local_key)?; let mut local_enr = build_enr::(&enr_key, config, enr_fork_id, spec)?; - use_or_load_enr(&enr_key, &mut local_enr, config, log)?; + use_or_load_enr(&enr_key, &mut local_enr, config)?; Ok(local_enr) } @@ -314,18 +312,19 @@ pub fn load_enr_from_disk(dir: &Path) -> Result { } /// Saves an ENR to disk -pub fn save_enr_to_disk(dir: &Path, enr: &Enr, log: &slog::Logger) { +pub fn save_enr_to_disk(dir: &Path, enr: &Enr) { let _ = std::fs::create_dir_all(dir); match File::create(dir.join(Path::new(ENR_FILENAME))) .and_then(|mut f| f.write_all(enr.to_base64().as_bytes())) { Ok(_) => { - debug!(log, "ENR written to disk"); + debug!("ENR written to disk"); } Err(e) => { warn!( - log, - "Could not write ENR to file"; "file" => format!("{:?}{:?}",dir, ENR_FILENAME), "error" => %e + file = format!("{:?}{:?}",dir, ENR_FILENAME), + error = %e, + "Could not write ENR to file" ); } } diff --git a/beacon_node/lighthouse_network/src/discovery/mod.rs b/beacon_node/lighthouse_network/src/discovery/mod.rs index 33c7775ae2..ad54c6b8b1 100644 --- a/beacon_node/lighthouse_network/src/discovery/mod.rs +++ b/beacon_node/lighthouse_network/src/discovery/mod.rs @@ -31,8 +31,8 @@ pub use libp2p::{ SubstreamProtocol, ToSwarm, }, }; +use logging::crit; use lru::LruCache; -use slog::{crit, debug, error, info, trace, warn}; use ssz::Encode; use std::num::NonZeroUsize; use std::{ @@ -45,6 +45,7 @@ use std::{ time::{Duration, Instant}, }; use tokio::sync::mpsc; +use tracing::{debug, error, info, trace, warn}; use types::{ChainSpec, EnrForkId, EthSpec}; mod subnet_predicate; @@ -192,8 +193,6 @@ pub struct Discovery { /// Specifies whether various port numbers should be updated after the discovery service has been started update_ports: UpdatePorts, - /// Logger for the discovery behaviour. - log: slog::Logger, spec: Arc, } @@ -203,11 +202,8 @@ impl Discovery { local_key: Keypair, config: &NetworkConfig, network_globals: Arc>, - log: &slog::Logger, spec: &ChainSpec, ) -> Result { - let log = log.clone(); - let enr_dir = match config.network_dir.to_str() { Some(path) => String::from(path), None => String::from(""), @@ -216,9 +212,11 @@ impl Discovery { let local_enr = network_globals.local_enr.read().clone(); let local_node_id = local_enr.node_id(); - info!(log, "ENR Initialised"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(), - "ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp4(), "tcp6" => ?local_enr.tcp6(), "udp6" => ?local_enr.udp6(), - "quic4" => ?local_enr.quic4(), "quic6" => ?local_enr.quic6() + info!( + enr = local_enr.to_base64(), seq = local_enr.seq(), id = %local_enr.node_id(), + ip4 = ?local_enr.ip4(), udp4= ?local_enr.udp4(), tcp4 = ?local_enr.tcp4(), tcp6 = ?local_enr.tcp6(), udp6 = ?local_enr.udp6(), + quic4 = ?local_enr.quic4(), quic6 = ?local_enr.quic6(), + "ENR Initialised" ); // convert the keypair into an ENR key @@ -234,22 +232,20 @@ impl Discovery { continue; } debug!( - log, - "Adding node to routing table"; - "node_id" => %bootnode_enr.node_id(), - "peer_id" => %bootnode_enr.peer_id(), - "ip" => ?bootnode_enr.ip4(), - "udp" => ?bootnode_enr.udp4(), - "tcp" => ?bootnode_enr.tcp4(), - "quic" => ?bootnode_enr.quic4() + node_id = %bootnode_enr.node_id(), + peer_id = %bootnode_enr.peer_id(), + ip = ?bootnode_enr.ip4(), + udp = ?bootnode_enr.udp4(), + tcp = ?bootnode_enr.tcp4(), + quic = bootnode_enr.quic4(), + "Adding node to routing table" ); let repr = bootnode_enr.to_string(); let _ = discv5.add_enr(bootnode_enr).map_err(|e| { error!( - log, - "Could not add peer to the local routing table"; - "addr" => repr, - "error" => e.to_string(), + addr = repr, + error = e.to_string(), + "Could not add peer to the local routing table" ) }); } @@ -257,14 +253,14 @@ impl Discovery { // Start the discv5 service and obtain an event stream let event_stream = if !config.disable_discovery { discv5.start().map_err(|e| e.to_string()).await?; - debug!(log, "Discovery service started"); + debug!("Discovery service started"); EventStream::Awaiting(Box::pin(discv5.event_stream())) } else { EventStream::InActive }; if !config.boot_nodes_multiaddr.is_empty() { - info!(log, "Contacting Multiaddr boot-nodes for their ENR"); + info!("Contacting Multiaddr boot-nodes for their ENR"); } // get futures for requesting the Enrs associated to these multiaddr and wait for their @@ -286,26 +282,28 @@ impl Discovery { match result { Ok(enr) => { debug!( - log, - "Adding node to routing table"; - "node_id" => %enr.node_id(), - "peer_id" => %enr.peer_id(), - "ip" => ?enr.ip4(), - "udp" => ?enr.udp4(), - "tcp" => ?enr.tcp4(), - "quic" => ?enr.quic4() + node_id = %enr.node_id(), + peer_id = %enr.peer_id(), + ip4 = ?enr.ip4(), + udp4 = ?enr.udp4(), + tcp4 = ?enr.tcp4(), + quic4 = ?enr.quic4(), + "Adding node to routing table" ); let _ = discv5.add_enr(enr).map_err(|e| { error!( - log, - "Could not add peer to the local routing table"; - "addr" => original_addr.to_string(), - "error" => e.to_string(), + addr = original_addr.to_string(), + error = e.to_string(), + "Could not add peer to the local routing table" ) }); } Err(e) => { - error!(log, "Error getting mapping to ENR"; "multiaddr" => original_addr.to_string(), "error" => e.to_string()) + error!( + multiaddr = original_addr.to_string(), + error = e.to_string(), + "Error getting mapping to ENR" + ) } } } @@ -327,7 +325,6 @@ impl Discovery { event_stream, started: !config.disable_discovery, update_ports, - log, enr_dir, spec: Arc::new(spec.clone()), }) @@ -358,7 +355,7 @@ impl Discovery { } // Immediately start a FindNode query let target_peers = std::cmp::min(FIND_NODE_QUERY_CLOSEST_PEERS, target_peers); - debug!(self.log, "Starting a peer discovery request"; "target_peers" => target_peers ); + debug!(target_peers, "Starting a peer discovery request"); self.find_peer_active = true; self.start_query(QueryType::FindPeers, target_peers, |_| true); } @@ -370,9 +367,8 @@ impl Discovery { return; } trace!( - self.log, - "Starting discovery query for subnets"; - "subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::>() + subnets = ?subnets_to_discover.iter().map(|s| s.subnet).collect::>(), + "Starting discovery query for subnets" ); for subnet in subnets_to_discover { self.add_subnet_query(subnet.subnet, subnet.min_ttl, 0); @@ -386,9 +382,8 @@ impl Discovery { if let Err(e) = self.discv5.add_enr(enr) { debug!( - self.log, - "Could not add peer to the local routing table"; - "error" => %e + error = %e, + "Could not add peer to the local routing table" ) } } @@ -427,7 +422,7 @@ impl Discovery { // replace the global version *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(true) } @@ -463,7 +458,7 @@ impl Discovery { // replace the global version *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(true) } @@ -475,7 +470,7 @@ impl Discovery { const IS_TCP: bool = false; if self.discv5.update_local_enr_socket(socket_addr, IS_TCP) { // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); } *self.network_globals.local_enr.write() = self.discv5.local_enr(); Ok(()) @@ -561,7 +556,7 @@ impl Discovery { *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(()) } @@ -575,10 +570,11 @@ impl Discovery { format!("{:?}", enr_fork_id.next_fork_epoch) }; - info!(self.log, "Updating the ENR fork version"; - "fork_digest" => ?enr_fork_id.fork_digest, - "next_fork_version" => ?enr_fork_id.next_fork_version, - "next_fork_epoch" => next_fork_epoch_log, + info!( + fork_digest = ?enr_fork_id.fork_digest, + next_fork_version = ?enr_fork_id.next_fork_version, + next_fork_epoch = next_fork_epoch_log, + "Updating the ENR fork version" ); let _ = self @@ -586,9 +582,8 @@ impl Discovery { .enr_insert::(ETH2_ENR_KEY, &enr_fork_id.as_ssz_bytes().into()) .map_err(|e| { warn!( - self.log, - "Could not update eth2 ENR field"; - "error" => ?e + error = ?e, + "Could not update eth2 ENR field" ) }); @@ -596,7 +591,7 @@ impl Discovery { *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); } // Bans a peer and it's associated seen IP addresses. @@ -642,10 +637,7 @@ impl Discovery { fn add_subnet_query(&mut self, subnet: Subnet, min_ttl: Option, retries: usize) { // remove the entry and complete the query if greater than the maximum search count if retries > MAX_DISCOVERY_RETRY { - debug!( - self.log, - "Subnet peer discovery did not find sufficient peers. Reached max retry limit" - ); + debug!("Subnet peer discovery did not find sufficient peers. Reached max retry limit"); return; } @@ -666,7 +658,7 @@ impl Discovery { } if !found { // update the metrics and insert into the queue. - trace!(self.log, "Queuing subnet query"; "subnet" => ?subnet, "retries" => retries); + trace!(?subnet, retries, "Queuing subnet query"); self.queued_queries.push_back(SubnetQuery { subnet, min_ttl, @@ -737,19 +729,21 @@ impl Discovery { .count(); if peers_on_subnet >= TARGET_SUBNET_PEERS { - debug!(self.log, "Discovery ignored"; - "reason" => "Already connected to desired peers", - "connected_peers_on_subnet" => peers_on_subnet, - "target_subnet_peers" => TARGET_SUBNET_PEERS, + debug!( + reason = "Already connected to desired peers", + connected_peers_on_subnet = peers_on_subnet, + target_subnet_peers = TARGET_SUBNET_PEERS, + "Discovery ignored" ); return false; } let target_peers = TARGET_SUBNET_PEERS.saturating_sub(peers_on_subnet); - trace!(self.log, "Discovery query started for subnet"; - "subnet_query" => ?subnet_query, - "connected_peers_on_subnet" => peers_on_subnet, - "peers_to_find" => target_peers, + trace!( + ?subnet_query, + connected_peers_on_subnet = peers_on_subnet, + peers_to_find = target_peers, + "Discovery query started for subnet" ); filtered_subnets.push(subnet_query.subnet); @@ -760,13 +754,11 @@ impl Discovery { // Only start a discovery query if we have a subnet to look for. if !filtered_subnet_queries.is_empty() { // build the subnet predicate as a combination of the eth2_fork_predicate and the subnet predicate - let subnet_predicate = - subnet_predicate::(filtered_subnets, &self.log, self.spec.clone()); + let subnet_predicate = subnet_predicate::(filtered_subnets, self.spec.clone()); debug!( - self.log, - "Starting grouped subnet query"; - "subnets" => ?filtered_subnet_queries, + subnets = ?filtered_subnet_queries, + "Starting grouped subnet query" ); self.start_query( QueryType::Subnet(filtered_subnet_queries), @@ -790,7 +782,7 @@ impl Discovery { let enr_fork_id = match self.local_enr().eth2() { Ok(v) => v, Err(e) => { - crit!(self.log, "Local ENR has no fork id"; "error" => e); + crit!(error = e, "Local ENR has no fork id"); return; } }; @@ -831,10 +823,10 @@ impl Discovery { self.find_peer_active = false; match query.result { Ok(r) if r.is_empty() => { - debug!(self.log, "Discovery query yielded no results."); + debug!("Discovery query yielded no results."); } Ok(r) => { - debug!(self.log, "Discovery query completed"; "peers_found" => r.len()); + debug!(peers_found = r.len(), "Discovery query completed"); let results = r .into_iter() .map(|enr| { @@ -846,7 +838,7 @@ impl Discovery { return Some(results); } Err(e) => { - warn!(self.log, "Discovery query failed"; "error" => %e); + warn!(error = %e, "Discovery query failed"); } } } @@ -855,13 +847,20 @@ impl Discovery { queries.iter().map(|query| query.subnet).collect(); match query.result { Ok(r) if r.is_empty() => { - debug!(self.log, "Grouped subnet discovery query yielded no results."; "subnets_searched_for" => ?subnets_searched_for); + debug!( + ?subnets_searched_for, + "Grouped subnet discovery query yielded no results." + ); queries.iter().for_each(|query| { self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1); }) } Ok(r) => { - debug!(self.log, "Peer grouped subnet discovery request completed"; "peers_found" => r.len(), "subnets_searched_for" => ?subnets_searched_for); + debug!( + peers_found = r.len(), + ?subnets_searched_for, + "Peer grouped subnet discovery request completed" + ); let mut mapped_results = HashMap::new(); @@ -888,11 +887,8 @@ impl Discovery { self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1); // Check the specific subnet against the enr - let subnet_predicate = subnet_predicate::( - vec![query.subnet], - &self.log, - self.spec.clone(), - ); + let subnet_predicate = + subnet_predicate::(vec![query.subnet], self.spec.clone()); r.clone() .into_iter() @@ -941,7 +937,7 @@ impl Discovery { } } Err(e) => { - warn!(self.log,"Grouped subnet discovery query failed"; "subnets_searched_for" => ?subnets_searched_for, "error" => %e); + warn!(?subnets_searched_for, error = %e,"Grouped subnet discovery query failed"); } } } @@ -1020,11 +1016,11 @@ impl NetworkBehaviour for Discovery { if let Poll::Ready(event_stream) = fut.poll_unpin(cx) { match event_stream { Ok(stream) => { - debug!(self.log, "Discv5 event stream ready"); + debug!("Discv5 event stream ready"); self.event_stream = EventStream::Present(stream); } Err(e) => { - slog::crit!(self.log, "Discv5 event stream failed"; "error" => %e); + crit!(error = %e, "Discv5 event stream failed"); self.event_stream = EventStream::InActive; } } @@ -1042,15 +1038,15 @@ impl NetworkBehaviour for Discovery { // log these to see if we are unnecessarily dropping discovered peers /* if enr.eth2() == self.local_enr().eth2() { - trace!(self.log, "Peer found in process of query"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); + trace!( "Peer found in process of query"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); } else { // this is temporary warning for debugging the DHT - warn!(self.log, "Found peer during discovery not on correct fork"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); + warn!( "Found peer during discovery not on correct fork"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); } */ } discv5::Event::SocketUpdated(socket_addr) => { - info!(self.log, "Address updated"; "ip" => %socket_addr.ip(), "udp_port" => %socket_addr.port()); + info!(ip = %socket_addr.ip(), udp_port = %socket_addr.port(),"Address updated"); metrics::inc_counter(&metrics::ADDRESS_UPDATE_COUNT); // Discv5 will have updated our local ENR. We save the updated version // to disk. @@ -1062,7 +1058,7 @@ impl NetworkBehaviour for Discovery { self.discv5.update_local_enr_socket(socket_addr, true); } let enr = self.discv5.local_enr(); - enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr, &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr); // update network globals *self.network_globals.local_enr.write() = enr; // A new UDP socket has been detected. @@ -1086,7 +1082,11 @@ impl NetworkBehaviour for Discovery { let addr = ev.addr; let listener_id = ev.listener_id; - trace!(self.log, "Received NewListenAddr event from swarm"; "listener_id" => ?listener_id, "addr" => ?addr); + trace!( + ?listener_id, + ?addr, + "Received NewListenAddr event from swarm" + ); let mut addr_iter = addr.iter(); @@ -1094,7 +1094,7 @@ impl NetworkBehaviour for Discovery { Some(Protocol::Ip4(_)) => match (addr_iter.next(), addr_iter.next()) { (Some(Protocol::Tcp(port)), None) => { if !self.update_ports.tcp4 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(multiaddr = ?addr, "Skipping ENR update"); return; } @@ -1102,21 +1102,21 @@ impl NetworkBehaviour for Discovery { } (Some(Protocol::Udp(port)), Some(Protocol::QuicV1)) => { if !self.update_ports.quic4 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } self.update_enr_quic_port(port, false) } _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (unsupported transport)"; "addr" => ?addr); + debug!(?addr, "Encountered unacceptable multiaddr for listening (unsupported transport)"); return; } }, Some(Protocol::Ip6(_)) => match (addr_iter.next(), addr_iter.next()) { (Some(Protocol::Tcp(port)), None) => { if !self.update_ports.tcp6 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } @@ -1124,19 +1124,22 @@ impl NetworkBehaviour for Discovery { } (Some(Protocol::Udp(port)), Some(Protocol::QuicV1)) => { if !self.update_ports.quic6 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } self.update_enr_quic_port(port, true) } _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (unsupported transport)"; "addr" => ?addr); + debug!(?addr, "Encountered unacceptable multiaddr for listening (unsupported transport)"); return; } }, _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (no IP)"; "addr" => ?addr); + debug!( + ?addr, + "Encountered unacceptable multiaddr for listening (no IP)" + ); return; } }; @@ -1145,10 +1148,10 @@ impl NetworkBehaviour for Discovery { match attempt_enr_update { Ok(true) => { - info!(self.log, "Updated local ENR"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(), "ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp4(), "tcp6" => ?local_enr.tcp6(), "udp6" => ?local_enr.udp6()) + info!(enr = local_enr.to_base64(), seq = local_enr.seq(), id = %local_enr.node_id(), ip4 = ?local_enr.ip4(), udp4 = ?local_enr.udp4(), tcp4 = ?local_enr.tcp4(), tcp6 = ?local_enr.tcp6(), udp6 = ?local_enr.udp6(),"Updated local ENR") } Ok(false) => {} // Nothing to do, ENR already configured - Err(e) => warn!(self.log, "Failed to update ENR"; "error" => ?e), + Err(e) => warn!(error = ?e,"Failed to update ENR"), } } _ => { @@ -1171,7 +1174,7 @@ impl Discovery { return; } // set peer as disconnected in discovery DHT - debug!(self.log, "Marking peer disconnected in DHT"; "peer_id" => %peer_id, "error" => %ClearDialError(error)); + debug!(%peer_id, error = %ClearDialError(error),"Marking peer disconnected in DHT"); self.disconnect_peer(&peer_id); } DialError::LocalPeerId { .. } @@ -1179,7 +1182,7 @@ impl Discovery { | DialError::Transport(_) | DialError::WrongPeerId { .. } => { // set peer as disconnected in discovery DHT - debug!(self.log, "Marking peer disconnected in DHT"; "peer_id" => %peer_id, "error" => %ClearDialError(error)); + debug!(%peer_id, error = %ClearDialError(error),"Marking peer disconnected in DHT"); self.disconnect_peer(&peer_id); } DialError::DialPeerConditionFalse(_) | DialError::Aborted => {} @@ -1193,23 +1196,10 @@ mod tests { use super::*; use crate::rpc::methods::{MetaData, MetaDataV2}; use libp2p::identity::secp256k1; - use slog::{o, Drain}; use types::{BitVector, MinimalEthSpec, SubnetId}; type E = MinimalEthSpec; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - async fn build_discovery() -> Discovery { let spec = Arc::new(ChainSpec::default()); let keypair = secp256k1::Keypair::generate(); @@ -1218,7 +1208,6 @@ mod tests { let config = Arc::new(config); let enr_key: CombinedKey = CombinedKey::from_secp256k1(&keypair); let enr: Enr = build_enr::(&enr_key, &config, &EnrForkId::default(), &spec).unwrap(); - let log = build_log(slog::Level::Debug, false); let globals = NetworkGlobals::new( enr, MetaData::V2(MetaDataV2 { @@ -1228,12 +1217,11 @@ mod tests { }), vec![], false, - &log, config.clone(), spec.clone(), ); let keypair = keypair.into(); - Discovery::new(keypair, &config, Arc::new(globals), &log, &spec) + Discovery::new(keypair, &config, Arc::new(globals), &spec) .await .unwrap() } diff --git a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs index 400a0c2d56..735ef5b0f2 100644 --- a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs +++ b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs @@ -1,22 +1,19 @@ //! The subnet predicate used for searching for a particular subnet. use super::*; use crate::types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield}; -use slog::trace; use std::ops::Deref; +use tracing::trace; use types::data_column_custody_group::compute_subnets_for_node; use types::ChainSpec; /// Returns the predicate for a given subnet. pub fn subnet_predicate( subnets: Vec, - log: &slog::Logger, spec: Arc, ) -> impl Fn(&Enr) -> bool + Send where E: EthSpec, { - let log_clone = log.clone(); - move |enr: &Enr| { let attestation_bitfield: EnrAttestationBitfield = match enr.attestation_bitfield::() { @@ -48,9 +45,8 @@ where if !predicate { trace!( - log_clone, - "Peer found but not on any of the desired subnets"; - "peer_id" => %enr.peer_id() + peer_id = %enr.peer_id(), + "Peer found but not on any of the desired subnets" ); } predicate diff --git a/beacon_node/lighthouse_network/src/listen_addr.rs b/beacon_node/lighthouse_network/src/listen_addr.rs index 53f7d9daca..3b0ff98b34 100644 --- a/beacon_node/lighthouse_network/src/listen_addr.rs +++ b/beacon_node/lighthouse_network/src/listen_addr.rs @@ -104,25 +104,3 @@ impl ListenAddress { }) } } - -impl slog::KV for ListenAddress { - fn serialize( - &self, - _record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - if let Some(v4_addr) = self.v4() { - serializer.emit_arguments("ip4_address", &format_args!("{}", v4_addr.addr))?; - serializer.emit_u16("disc4_port", v4_addr.disc_port)?; - serializer.emit_u16("quic4_port", v4_addr.quic_port)?; - serializer.emit_u16("tcp4_port", v4_addr.tcp_port)?; - } - if let Some(v6_addr) = self.v6() { - serializer.emit_arguments("ip6_address", &format_args!("{}", v6_addr.addr))?; - serializer.emit_u16("disc6_port", v6_addr.disc_port)?; - serializer.emit_u16("quic6_port", v6_addr.quic_port)?; - serializer.emit_u16("tcp6_port", v6_addr.tcp_port)?; - } - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 07c4be7959..4b48c7e625 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -11,12 +11,12 @@ use libp2p::identify::Info as IdentifyInfo; use lru_cache::LRUTimeCache; use peerdb::{BanOperation, BanResult, ScoreUpdateResult}; use rand::seq::SliceRandom; -use slog::{debug, error, trace, warn}; use smallvec::SmallVec; use std::{ sync::Arc, time::{Duration, Instant}, }; +use tracing::{debug, error, trace, warn}; use types::{DataColumnSubnetId, EthSpec, SyncSubnetId}; pub use libp2p::core::Multiaddr; @@ -114,8 +114,7 @@ pub struct PeerManager { metrics_enabled: bool, /// Keeps track of whether the QUIC protocol is enabled or not. quic_enabled: bool, - /// The logger associated with the `PeerManager`. - log: slog::Logger, + trusted_peers: HashSet, } /// The events that the `PeerManager` outputs (requests). @@ -150,7 +149,6 @@ impl PeerManager { pub fn new( cfg: config::Config, network_globals: Arc>, - log: &slog::Logger, ) -> Result { let config::Config { discovery_enabled, @@ -195,7 +193,7 @@ impl PeerManager { discovery_enabled, metrics_enabled, quic_enabled, - log: log.clone(), + trusted_peers: Default::default(), }) } @@ -209,7 +207,7 @@ impl PeerManager { pub fn goodbye_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason, source: ReportSource) { // Update the sync status if required if let Some(info) = self.network_globals.peers.write().peer_info_mut(peer_id) { - debug!(self.log, "Sending goodbye to peer"; "peer_id" => %peer_id, "reason" => %reason, "score" => %info.score()); + debug!(%peer_id, %reason, score = %info.score(), "Sending goodbye to peer"); if matches!(reason, GoodbyeReason::IrrelevantNetwork) { info.update_sync_status(SyncStatus::IrrelevantPeer); } @@ -369,7 +367,7 @@ impl PeerManager { .update_min_ttl(&peer_id, min_ttl); } if self.dial_peer(enr) { - debug!(self.log, "Added discovered ENR peer to dial queue"; "peer_id" => %peer_id); + debug!(%peer_id, "Added discovered ENR peer to dial queue"); to_dial_peers += 1; } } @@ -382,7 +380,10 @@ impl PeerManager { // reach out target. To prevent the infinite loop, if a query returns no useful peers, we // will cancel the recursiveness and wait for the heartbeat to trigger another query latter. if results_count > 0 && to_dial_peers == 0 { - debug!(self.log, "Skipping recursive discovery query after finding no useful results"; "results" => results_count); + debug!( + results = results_count, + "Skipping recursive discovery query after finding no useful results" + ); metrics::inc_counter(&metrics::DISCOVERY_NO_USEFUL_ENRS); } else { // Queue another discovery if we need to @@ -481,16 +482,21 @@ impl PeerManager { if previous_kind != peer_info.client().kind || *peer_info.listening_addresses() != previous_listening_addresses { - debug!(self.log, "Identified Peer"; "peer" => %peer_id, - "protocol_version" => &info.protocol_version, - "agent_version" => &info.agent_version, - "listening_addresses" => ?info.listen_addrs, - "observed_address" => ?info.observed_addr, - "protocols" => ?info.protocols + debug!( + %peer_id, + protocol_version = &info.protocol_version, + agent_version = &info.agent_version, + listening_addresses = ?info.listen_addrs, + observed_address = ?info.observed_addr, + protocols = ?info.protocols, + "Identified Peer" ); } } else { - error!(self.log, "Received an Identify response from an unknown peer"; "peer_id" => peer_id.to_string()); + error!( + peer_id = peer_id.to_string(), + "Received an Identify response from an unknown peer" + ); } } @@ -506,8 +512,7 @@ impl PeerManager { ) { let client = self.network_globals.client(peer_id); let score = self.network_globals.peers.read().score(peer_id); - debug!(self.log, "RPC Error"; "protocol" => %protocol, "err" => %err, "client" => %client, - "peer_id" => %peer_id, "score" => %score, "direction" => ?direction); + debug!(%protocol, %err, %client, %peer_id, %score, ?direction, "RPC Error"); metrics::inc_counter_vec( &metrics::TOTAL_RPC_ERRORS_PER_CLIENT, &[ @@ -524,7 +529,7 @@ impl PeerManager { PeerAction::MidToleranceError } RPCError::InternalError(e) => { - debug!(self.log, "Internal RPC Error"; "error" => %e, "peer_id" => %peer_id); + debug!(error = %e, %peer_id, "Internal RPC Error"); return; } RPCError::HandlerRejected => PeerAction::Fatal, @@ -617,7 +622,7 @@ impl PeerManager { RPCError::StreamTimeout => match direction { ConnectionDirection::Incoming => { // There was a timeout responding to a peer. - debug!(self.log, "Timed out responding to RPC Request"; "peer_id" => %peer_id); + debug!(%peer_id, "Timed out responding to RPC Request"); return; } ConnectionDirection::Outgoing => match protocol { @@ -656,7 +661,7 @@ impl PeerManager { if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) { // received a ping // reset the to-ping timer for this peer - trace!(self.log, "Received a ping request"; "peer_id" => %peer_id, "seq_no" => seq); + trace!(%peer_id, seq_no = seq, "Received a ping request"); match peer_info.connection_direction() { Some(ConnectionDirection::Incoming) => { self.inbound_ping_peers.insert(*peer_id); @@ -665,26 +670,23 @@ impl PeerManager { self.outbound_ping_peers.insert(*peer_id); } None => { - warn!(self.log, "Received a ping from a peer with an unknown connection direction"; "peer_id" => %peer_id); + warn!(%peer_id, "Received a ping from a peer with an unknown connection direction"); } } // if the sequence number is unknown send an update the meta data of the peer. if let Some(meta_data) = &peer_info.meta_data() { if *meta_data.seq_number() < seq { - trace!(self.log, "Requesting new metadata from peer"; - "peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "ping_seq_no" => seq); + trace!(%peer_id, known_seq_no = meta_data.seq_number(), ping_seq_no = seq, "Requesting new metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { // if we don't know the meta-data, request it - debug!(self.log, "Requesting first metadata from peer"; - "peer_id" => %peer_id); + debug!(%peer_id, "Requesting first metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { - error!(self.log, "Received a PING from an unknown peer"; - "peer_id" => %peer_id); + error!(%peer_id, "Received a PING from an unknown peer"); } } @@ -696,18 +698,16 @@ impl PeerManager { // if the sequence number is unknown send update the meta data of the peer. if let Some(meta_data) = &peer_info.meta_data() { if *meta_data.seq_number() < seq { - trace!(self.log, "Requesting new metadata from peer"; - "peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "pong_seq_no" => seq); + trace!(%peer_id, known_seq_no = meta_data.seq_number(), pong_seq_no = seq, "Requesting new metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { // if we don't know the meta-data, request it - trace!(self.log, "Requesting first metadata from peer"; - "peer_id" => %peer_id); + trace!(%peer_id, "Requesting first metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { - error!(self.log, "Received a PONG from an unknown peer"; "peer_id" => %peer_id); + error!(%peer_id, "Received a PONG from an unknown peer"); } } @@ -718,18 +718,15 @@ impl PeerManager { if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) { if let Some(known_meta_data) = &peer_info.meta_data() { if *known_meta_data.seq_number() < *meta_data.seq_number() { - trace!(self.log, "Updating peer's metadata"; - "peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number()); + trace!(%peer_id, known_seq_no = known_meta_data.seq_number(), new_seq_no = meta_data.seq_number(), "Updating peer's metadata"); } else { - trace!(self.log, "Received old metadata"; - "peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number()); + trace!(%peer_id, known_seq_no = known_meta_data.seq_number(), new_seq_no = meta_data.seq_number(), "Received old metadata"); // Updating metadata even in this case to prevent storing // incorrect `attnets/syncnets` for a peer } } else { // we have no meta-data for this peer, update - debug!(self.log, "Obtained peer's metadata"; - "peer_id" => %peer_id, "new_seq_no" => meta_data.seq_number()); + debug!(%peer_id, new_seq_no = meta_data.seq_number(), "Obtained peer's metadata"); } let custody_group_count_opt = meta_data.custody_group_count().copied().ok(); @@ -749,10 +746,9 @@ impl PeerManager { .cloned() .unwrap_or_else(|| { warn!( - self.log, - "Custody group not found in subnet mapping"; - "custody_index" => custody_index, - "peer_id" => %peer_id + %custody_index, + %peer_id, + "Custody group not found in subnet mapping" ); vec![] }) @@ -761,11 +757,12 @@ impl PeerManager { peer_info.set_custody_subnets(custody_subnets); } Err(err) => { - debug!(self.log, "Unable to compute peer custody groups from metadata"; - "info" => "Sending goodbye to peer", - "peer_id" => %peer_id, - "custody_group_count" => custody_group_count, - "error" => ?err, + debug!( + info = "Sending goodbye to peer", + peer_id = %peer_id, + custody_group_count, + error = ?err, + "Unable to compute peer custody groups from metadata" ); invalid_meta_data = true; } @@ -773,8 +770,7 @@ impl PeerManager { } } } else { - error!(self.log, "Received METADATA from an unknown peer"; - "peer_id" => %peer_id); + error!(%peer_id, "Received METADATA from an unknown peer"); } // Disconnect peers with invalid metadata and find other peers instead. @@ -866,7 +862,7 @@ impl PeerManager { let mut peerdb = self.network_globals.peers.write(); if peerdb.ban_status(peer_id).is_some() { // don't connect if the peer is banned - error!(self.log, "Connection has been allowed to a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Connection has been allowed to a banned peer"); } match connection { @@ -894,7 +890,7 @@ impl PeerManager { } // Gracefully disconnects a peer without banning them. - fn disconnect_peer(&mut self, peer_id: PeerId, reason: GoodbyeReason) { + pub fn disconnect_peer(&mut self, peer_id: PeerId, reason: GoodbyeReason) { self.events .push(PeerManagerEvent::DisconnectPeer(peer_id, reason)); self.network_globals @@ -934,15 +930,21 @@ impl PeerManager { // request the subnet query from discovery if !subnets_to_discover.is_empty() { debug!( - self.log, - "Making subnet queries for maintaining sync committee peers"; - "subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::>() + subnets = ?subnets_to_discover.iter().map(|s| s.subnet).collect::>(), + "Making subnet queries for maintaining sync committee peers" ); self.events .push(PeerManagerEvent::DiscoverSubnetPeers(subnets_to_discover)); } } + fn maintain_trusted_peers(&mut self) { + let trusted_peers = self.trusted_peers.clone(); + for trusted_peer in trusted_peers { + self.dial_peer(trusted_peer); + } + } + /// This function checks the status of our current peers and optionally requests a discovery /// query if we need to find more peers to maintain the current number of peers fn maintain_peer_count(&mut self, dialing_peers: usize) { @@ -965,7 +967,13 @@ impl PeerManager { if wanted_peers != 0 { // We need more peers, re-queue a discovery lookup. - debug!(self.log, "Starting a new peer discovery query"; "connected" => peer_count, "target" => self.target_peers, "outbound" => outbound_only_peer_count, "wanted" => wanted_peers); + debug!( + connected = peer_count, + target = self.target_peers, + outbound = outbound_only_peer_count, + wanted = wanted_peers, + "Starting a new peer discovery query" + ); self.events .push(PeerManagerEvent::DiscoverPeers(wanted_peers)); } @@ -1234,6 +1242,7 @@ impl PeerManager { fn heartbeat(&mut self) { // Optionally run a discovery query if we need more peers. self.maintain_peer_count(0); + self.maintain_trusted_peers(); // Cleans up the connection state of dialing peers. // Libp2p dials peer-ids, but sometimes the response is from another peer-id or libp2p @@ -1470,6 +1479,14 @@ impl PeerManager { ) }) } + + pub fn add_trusted_peer(&mut self, enr: Enr) { + self.trusted_peers.insert(enr); + } + + pub fn remove_trusted_peer(&mut self, enr: Enr) { + self.trusted_peers.remove(&enr); + } } enum ConnectingType { @@ -1491,21 +1508,8 @@ enum ConnectingType { mod tests { use super::*; use crate::NetworkConfig; - use slog::{o, Drain}; use types::MainnetEthSpec as E; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - async fn build_peer_manager(target_peer_count: usize) -> PeerManager { build_peer_manager_with_trusted_peers(vec![], target_peer_count).await } @@ -1523,10 +1527,9 @@ mod tests { target_peers: target_peer_count, ..Default::default() }); - let log = build_log(slog::Level::Debug, false); let spec = Arc::new(E::default_spec()); - let globals = NetworkGlobals::new_test_globals(trusted_peers, &log, network_config, spec); - PeerManager::new(config, Arc::new(globals), &log).unwrap() + let globals = NetworkGlobals::new_test_globals(trusted_peers, network_config, spec); + PeerManager::new(config, Arc::new(globals)).unwrap() } #[tokio::test] diff --git a/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs b/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs index abafb200be..1ad55ce5c4 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs @@ -13,7 +13,7 @@ use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; use libp2p::swarm::dummy::ConnectionHandler; use libp2p::swarm::{ConnectionDenied, ConnectionId, NetworkBehaviour, ToSwarm}; pub use metrics::{set_gauge_vec, NAT_OPEN}; -use slog::{debug, error, trace}; +use tracing::{debug, error, trace}; use types::EthSpec; use crate::discovery::enr_ext::EnrExt; @@ -54,7 +54,10 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Ping(peer_id)); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for inbound peers to ping"; "error" => e.to_string()) + error!( + error = e.to_string(), + "Failed to check for inbound peers to ping" + ) } Poll::Ready(None) | Poll::Pending => break, } @@ -67,7 +70,10 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Ping(peer_id)); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for outbound peers to ping"; "error" => e.to_string()) + error!( + error = e.to_string(), + "Failed to check for outbound peers to ping" + ) } Poll::Ready(None) | Poll::Pending => break, } @@ -84,7 +90,7 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Status(peer_id)) } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for peers to ping"; "error" => e.to_string()) + error!(error = e.to_string(), "Failed to check for peers to ping") } Poll::Ready(None) | Poll::Pending => break, } @@ -109,7 +115,7 @@ impl NetworkBehaviour for PeerManager { ] .concat(); - debug!(self.log, "Dialing peer"; "peer_id"=> %enr.peer_id(), "multiaddrs" => ?multiaddrs); + debug!(peer_id = %enr.peer_id(), ?multiaddrs, "Dialing peer"); return Poll::Ready(ToSwarm::Dial { opts: DialOpts::peer_id(enr.peer_id()) .condition(PeerCondition::Disconnected) @@ -141,7 +147,7 @@ impl NetworkBehaviour for PeerManager { error, connection_id: _, }) => { - debug!(self.log, "Failed to dial peer"; "peer_id"=> ?peer_id, "error" => %ClearDialError(error)); + debug!(?peer_id, error = %ClearDialError(error),"Failed to dial peer"); self.on_dial_failure(peer_id); } _ => { @@ -186,7 +192,7 @@ impl NetworkBehaviour for PeerManager { _local_addr: &libp2p::Multiaddr, remote_addr: &libp2p::Multiaddr, ) -> Result, ConnectionDenied> { - trace!(self.log, "Inbound connection"; "peer_id" => %peer_id, "multiaddr" => %remote_addr); + trace!(%peer_id, multiaddr = %remote_addr, "Inbound connection"); // We already checked if the peer was banned on `handle_pending_inbound_connection`. if self.ban_status(&peer_id).is_some() { return Err(ConnectionDenied::new( @@ -201,7 +207,7 @@ impl NetworkBehaviour for PeerManager { .peers .read() .peer_info(&peer_id) - .map_or(true, |peer| !peer.has_future_duty()) + .is_none_or(|peer| !peer.has_future_duty()) { return Err(ConnectionDenied::new( "Connection to peer rejected: too many connections", @@ -227,9 +233,9 @@ impl NetworkBehaviour for PeerManager { _role_override: libp2p::core::Endpoint, _port_use: PortUse, ) -> Result, libp2p::swarm::ConnectionDenied> { - trace!(self.log, "Outbound connection"; "peer_id" => %peer_id, "multiaddr" => %addr); + trace!(%peer_id, multiaddr = %addr,"Outbound connection"); if let Some(cause) = self.ban_status(&peer_id) { - error!(self.log, "Connected a banned peer. Rejecting connection"; "peer_id" => %peer_id); + error!(%peer_id, "Connected a banned peer. Rejecting connection"); return Err(ConnectionDenied::new(cause)); } @@ -240,7 +246,7 @@ impl NetworkBehaviour for PeerManager { .peers .read() .peer_info(&peer_id) - .map_or(true, |peer| !peer.has_future_duty()) + .is_none_or(|peer| !peer.has_future_duty()) { return Err(ConnectionDenied::new( "Connection to peer rejected: too many connections", @@ -258,9 +264,11 @@ impl PeerManager { endpoint: &ConnectedPoint, _other_established: usize, ) { - debug!(self.log, "Connection established"; "peer_id" => %peer_id, - "multiaddr" => %endpoint.get_remote_address(), - "connection" => ?endpoint.to_endpoint() + debug!( + multiaddr = %endpoint.get_remote_address(), + connection = ?endpoint.to_endpoint(), + %peer_id, + "Connection established" ); // Update the prometheus metrics @@ -309,7 +317,7 @@ impl PeerManager { // Inform the application. self.events .push(PeerManagerEvent::PeerDisconnected(peer_id)); - debug!(self.log, "Peer disconnected"; "peer_id" => %peer_id); + debug!(%peer_id,"Peer disconnected"); } // NOTE: It may be the case that a rejected node, due to too many peers is disconnected diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs index 8e5d6121e0..54e74457b8 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs @@ -2,17 +2,18 @@ use crate::discovery::enr::PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY; use crate::discovery::{peer_id_to_node_id, CombinedKey}; use crate::{metrics, multiaddr::Multiaddr, types::Subnet, Enr, EnrExt, Gossipsub, PeerId}; use itertools::Itertools; +use logging::crit; use peer_info::{ConnectionDirection, PeerConnectionStatus, PeerInfo}; use score::{PeerAction, ReportSource, Score, ScoreState}; -use slog::{crit, debug, error, trace, warn}; use std::net::IpAddr; use std::time::Instant; use std::{cmp::Ordering, fmt::Display}; use std::{ - collections::{HashMap, HashSet}, + collections::{hash_map::Entry, HashMap, HashSet}, fmt::Formatter, }; use sync_status::SyncStatus; +use tracing::{debug, error, trace, warn}; use types::data_column_custody_group::compute_subnets_for_node; use types::{ChainSpec, DataColumnSubnetId, EthSpec}; @@ -44,19 +45,16 @@ pub struct PeerDB { banned_peers_count: BannedPeersCount, /// Specifies if peer scoring is disabled. disable_peer_scoring: bool, - /// PeerDB's logger - log: slog::Logger, } impl PeerDB { - pub fn new(trusted_peers: Vec, disable_peer_scoring: bool, log: &slog::Logger) -> Self { + pub fn new(trusted_peers: Vec, disable_peer_scoring: bool) -> Self { // Initialize the peers hashmap with trusted peers let peers = trusted_peers .into_iter() .map(|peer_id| (peer_id, PeerInfo::trusted_peer_info())) .collect(); Self { - log: log.clone(), disconnected_peers: 0, banned_peers_count: BannedPeersCount::default(), disable_peer_scoring, @@ -79,6 +77,33 @@ impl PeerDB { self.peers.iter() } + pub fn set_trusted_peer(&mut self, enr: Enr) { + match self.peers.entry(enr.peer_id()) { + Entry::Occupied(mut info) => { + let entry = info.get_mut(); + entry.score = Score::max_score(); + entry.is_trusted = true; + } + Entry::Vacant(entry) => { + entry.insert(PeerInfo::trusted_peer_info()); + } + } + } + + pub fn unset_trusted_peer(&mut self, enr: Enr) { + if let Some(info) = self.peers.get_mut(&enr.peer_id()) { + info.is_trusted = false; + info.score = Score::default(); + } + } + + pub fn trusted_peers(&self) -> Vec { + self.peers + .iter() + .filter_map(|(id, info)| if info.is_trusted { Some(*id) } else { None }) + .collect() + } + /// Gives the ids of all known peers. pub fn peer_ids(&self) -> impl Iterator { self.peers.keys() @@ -385,15 +410,15 @@ impl PeerDB { // Update scores info.score_update(); - match Self::handle_score_transition(previous_state, peer_id, info, &self.log) { + match Self::handle_score_transition(previous_state, peer_id, info) { // A peer should not be able to be banned from a score update. ScoreTransitionResult::Banned => { - error!(self.log, "Peer has been banned in an update"; "peer_id" => %peer_id) + error!(%peer_id, "Peer has been banned in an update"); } // A peer should not be able to transition to a disconnected state from a healthy // state in a score update. ScoreTransitionResult::Disconnected => { - error!(self.log, "Peer has been disconnected in an update"; "peer_id" => %peer_id) + error!(%peer_id, "Peer has been disconnected in an update"); } ScoreTransitionResult::Unbanned => { peers_to_unban.push(*peer_id); @@ -466,7 +491,7 @@ impl PeerDB { actions.push(( *peer_id, - Self::handle_score_transition(previous_state, peer_id, info, &self.log), + Self::handle_score_transition(previous_state, peer_id, info), )); } @@ -537,15 +562,13 @@ impl PeerDB { &metrics::PEER_ACTION_EVENTS_PER_CLIENT, &[info.client().kind.as_ref(), action.as_ref(), source.into()], ); - let result = - Self::handle_score_transition(previous_state, peer_id, info, &self.log); + let result = Self::handle_score_transition(previous_state, peer_id, info); if previous_state == info.score_state() { debug!( - self.log, - "Peer score adjusted"; - "msg" => %msg, - "peer_id" => %peer_id, - "score" => %info.score() + %msg, + %peer_id, + score = %info.score(), + "Peer score adjusted" ); } match result { @@ -567,10 +590,9 @@ impl PeerDB { ScoreTransitionResult::NoAction => ScoreUpdateResult::NoAction, ScoreTransitionResult::Unbanned => { error!( - self.log, - "Report peer action lead to an unbanning"; - "msg" => %msg, - "peer_id" => %peer_id + %msg, + %peer_id, + "Report peer action lead to an unbanning" ); ScoreUpdateResult::NoAction } @@ -578,10 +600,9 @@ impl PeerDB { } None => { debug!( - self.log, - "Reporting a peer that doesn't exist"; - "msg" => %msg, - "peer_id" =>%peer_id + %msg, + %peer_id, + "Reporting a peer that doesn't exist" ); ScoreUpdateResult::NoAction } @@ -601,7 +622,7 @@ impl PeerDB { .checked_duration_since(Instant::now()) .map(|duration| duration.as_secs()) .unwrap_or_else(|| 0); - debug!(self.log, "Updating the time a peer is required for"; "peer_id" => %peer_id, "future_min_ttl_secs" => min_ttl_secs); + debug!(%peer_id, future_min_ttl_secs = min_ttl_secs, "Updating the time a peer is required for"); } } @@ -625,12 +646,14 @@ impl PeerDB { /// min_ttl than what's given. // VISIBILITY: The behaviour is able to adjust subscriptions. pub(crate) fn extend_peers_on_subnet(&mut self, subnet: &Subnet, min_ttl: Instant) { - let log = &self.log; - self.peers.iter_mut() + self.peers + .iter_mut() .filter(move |(_, info)| { - info.is_connected() && info.on_subnet_metadata(subnet) && info.on_subnet_gossipsub(subnet) + info.is_connected() + && info.on_subnet_metadata(subnet) + && info.on_subnet_gossipsub(subnet) }) - .for_each(|(peer_id,info)| { + .for_each(|(peer_id, info)| { if info.min_ttl().is_none() || Some(&min_ttl) > info.min_ttl() { info.set_min_ttl(min_ttl); } @@ -638,7 +661,7 @@ impl PeerDB { .checked_duration_since(Instant::now()) .map(|duration| duration.as_secs()) .unwrap_or_else(|| 0); - trace!(log, "Updating minimum duration a peer is required for"; "peer_id" => %peer_id, "min_ttl" => min_ttl_secs); + trace!(%peer_id, min_ttl_secs, "Updating minimum duration a peer is required for"); }); } @@ -740,7 +763,6 @@ impl PeerDB { peer_id: &PeerId, new_state: NewConnectionState, ) -> Option { - let log_ref = &self.log; let info = self.peers.entry(*peer_id).or_insert_with(|| { // If we are not creating a new connection (or dropping a current inbound connection) log a warning indicating we are updating a // connection state for an unknown peer. @@ -752,8 +774,7 @@ impl PeerDB { | NewConnectionState::Disconnected { .. } // Dialing a peer that responds by a different ID can be immediately // disconnected without having being stored in the db before ) { - warn!(log_ref, "Updating state of unknown peer"; - "peer_id" => %peer_id, "new_state" => ?new_state); + warn!(%peer_id, ?new_state, "Updating state of unknown peer"); } if self.disable_peer_scoring { PeerInfo::trusted_peer_info() @@ -768,7 +789,7 @@ impl PeerDB { ScoreState::Banned => {} _ => { // If score isn't low enough to ban, this function has been called incorrectly. - error!(self.log, "Banning a peer with a good score"; "peer_id" => %peer_id); + error!(%peer_id, "Banning a peer with a good score"); info.apply_peer_action_to_score(score::PeerAction::Fatal); } } @@ -799,13 +820,13 @@ impl PeerDB { self.disconnected_peers = self.disconnected_peers.saturating_sub(1); } PeerConnectionStatus::Banned { .. } => { - error!(self.log, "Accepted a connection from a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Accepted a connection from a banned peer"); // TODO: check if this happens and report the unban back self.banned_peers_count .remove_banned_peer(info.seen_ip_addresses()); } PeerConnectionStatus::Disconnecting { .. } => { - warn!(self.log, "Connected to a disconnecting peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Connected to a disconnecting peer"); } PeerConnectionStatus::Unknown | PeerConnectionStatus::Connected { .. } @@ -827,7 +848,7 @@ impl PeerDB { (old_state, NewConnectionState::Dialing { enr }) => { match old_state { PeerConnectionStatus::Banned { .. } => { - warn!(self.log, "Dialing a banned peer"; "peer_id" => %peer_id); + warn!(%peer_id, "Dialing a banned peer"); self.banned_peers_count .remove_banned_peer(info.seen_ip_addresses()); } @@ -835,13 +856,13 @@ impl PeerDB { self.disconnected_peers = self.disconnected_peers.saturating_sub(1); } PeerConnectionStatus::Connected { .. } => { - warn!(self.log, "Dialing an already connected peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing an already connected peer"); } PeerConnectionStatus::Dialing { .. } => { - warn!(self.log, "Dialing an already dialing peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing an already dialing peer"); } PeerConnectionStatus::Disconnecting { .. } => { - warn!(self.log, "Dialing a disconnecting peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing a disconnecting peer"); } PeerConnectionStatus::Unknown => {} // default behaviour } @@ -851,7 +872,7 @@ impl PeerDB { } if let Err(e) = info.set_dialing_peer() { - error!(self.log, "{}", e; "peer_id" => %peer_id); + error!(%peer_id, e); } } @@ -907,7 +928,7 @@ impl PeerDB { * Handles the transition to a disconnecting state */ (PeerConnectionStatus::Banned { .. }, NewConnectionState::Disconnecting { to_ban }) => { - error!(self.log, "Disconnecting from a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Disconnecting from a banned peer"); info.set_connection_status(PeerConnectionStatus::Disconnecting { to_ban }); } ( @@ -951,13 +972,13 @@ impl PeerDB { (PeerConnectionStatus::Disconnecting { .. }, NewConnectionState::Banned) => { // NOTE: This can occur due a rapid downscore of a peer. It goes through the // disconnection phase and straight into banning in a short time-frame. - debug!(log_ref, "Banning peer that is currently disconnecting"; "peer_id" => %peer_id); + debug!(%peer_id, "Banning peer that is currently disconnecting"); // Ban the peer once the disconnection process completes. info.set_connection_status(PeerConnectionStatus::Disconnecting { to_ban: true }); return Some(BanOperation::PeerDisconnecting); } (PeerConnectionStatus::Banned { .. }, NewConnectionState::Banned) => { - error!(log_ref, "Banning already banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Banning already banned peer"); let known_banned_ips = self.banned_peers_count.banned_ips(); let banned_ips = info .seen_ip_addresses() @@ -975,7 +996,7 @@ impl PeerDB { } (PeerConnectionStatus::Unknown, NewConnectionState::Banned) => { // shift the peer straight to banned - warn!(log_ref, "Banning a peer of unknown connection state"; "peer_id" => %peer_id); + warn!(%peer_id, "Banning a peer of unknown connection state"); self.banned_peers_count .add_banned_peer(info.seen_ip_addresses()); info.set_connection_status(PeerConnectionStatus::Banned { @@ -996,15 +1017,15 @@ impl PeerDB { */ (old_state, NewConnectionState::Unbanned) => { if matches!(info.score_state(), ScoreState::Banned) { - error!(self.log, "Unbanning a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Unbanning a banned peer"); } match old_state { PeerConnectionStatus::Unknown | PeerConnectionStatus::Connected { .. } => { - error!(self.log, "Unbanning a connected peer"; "peer_id" => %peer_id); + error!(%peer_id, "Unbanning a connected peer"); } PeerConnectionStatus::Disconnected { .. } | PeerConnectionStatus::Disconnecting { .. } => { - debug!(self.log, "Unbanning disconnected or disconnecting peer"; "peer_id" => %peer_id); + debug!(%peer_id, "Unbanning disconnected or disconnecting peer"); } // These are odd but fine. PeerConnectionStatus::Dialing { .. } => {} // Also odd but acceptable PeerConnectionStatus::Banned { since } => { @@ -1073,15 +1094,12 @@ impl PeerDB { Some((*id, unbanned_ips)) } else { // If there is no minimum, this is a coding error. - crit!( - self.log, - "banned_peers > MAX_BANNED_PEERS despite no banned peers in db!" - ); + crit!("banned_peers > MAX_BANNED_PEERS despite no banned peers in db!"); // reset banned_peers this will also exit the loop self.banned_peers_count = BannedPeersCount::default(); None } { - debug!(self.log, "Removing old banned peer"; "peer_id" => %to_drop); + debug!(peer_id = %to_drop, "Removing old banned peer"); self.peers.remove(&to_drop); unbanned_peers.push((to_drop, unbanned_ips)) } @@ -1100,7 +1118,11 @@ impl PeerDB { .min_by_key(|(_, age)| *age) .map(|(id, _)| *id) { - debug!(self.log, "Removing old disconnected peer"; "peer_id" => %to_drop, "disconnected_size" => self.disconnected_peers.saturating_sub(1)); + debug!( + peer_id = %to_drop, + disconnected_size = self.disconnected_peers.saturating_sub(1), + "Removing old disconnected peer" + ); self.peers.remove(&to_drop); } // If there is no minimum, this is a coding error. For safety we decrease @@ -1117,15 +1139,19 @@ impl PeerDB { previous_state: ScoreState, peer_id: &PeerId, info: &PeerInfo, - log: &slog::Logger, ) -> ScoreTransitionResult { match (info.score_state(), previous_state) { (ScoreState::Banned, ScoreState::Healthy | ScoreState::ForcedDisconnect) => { - debug!(log, "Peer has been banned"; "peer_id" => %peer_id, "score" => %info.score()); + debug!(%peer_id, score = %info.score(), "Peer has been banned"); ScoreTransitionResult::Banned } (ScoreState::ForcedDisconnect, ScoreState::Banned | ScoreState::Healthy) => { - debug!(log, "Peer transitioned to forced disconnect score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to forced disconnect score state" + ); // disconnect the peer if it's currently connected or dialing if info.is_connected_or_dialing() { ScoreTransitionResult::Disconnected @@ -1138,11 +1164,21 @@ impl PeerDB { } } (ScoreState::Healthy, ScoreState::ForcedDisconnect) => { - debug!(log, "Peer transitioned to healthy score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to healthy score state" + ); ScoreTransitionResult::NoAction } (ScoreState::Healthy, ScoreState::Banned) => { - debug!(log, "Peer transitioned to healthy score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to healthy score state" + ); // unban the peer if it was previously banned. ScoreTransitionResult::Unbanned } @@ -1309,24 +1345,11 @@ impl BannedPeersCount { mod tests { use super::*; use libp2p::core::multiaddr::Protocol; - use slog::{o, Drain}; use std::net::{Ipv4Addr, Ipv6Addr}; use types::MinimalEthSpec; type M = MinimalEthSpec; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - fn add_score(db: &mut PeerDB, peer_id: &PeerId, score: f64) { if let Some(info) = db.peer_info_mut(peer_id) { info.add_to_score(score); @@ -1340,8 +1363,7 @@ mod tests { } fn get_db() -> PeerDB { - let log = build_log(slog::Level::Debug, false); - PeerDB::new(vec![], false, &log) + PeerDB::new(vec![], false) } #[test] @@ -2039,8 +2061,7 @@ mod tests { #[allow(clippy::float_cmp)] fn test_trusted_peers_score() { let trusted_peer = PeerId::random(); - let log = build_log(slog::Level::Debug, false); - let mut pdb: PeerDB = PeerDB::new(vec![trusted_peer], false, &log); + let mut pdb: PeerDB = PeerDB::new(vec![trusted_peer], false); pdb.connect_ingoing(&trusted_peer, "/ip4/0.0.0.0".parse().unwrap(), None); @@ -2063,8 +2084,7 @@ mod tests { #[test] fn test_disable_peer_scoring() { let peer = PeerId::random(); - let log = build_log(slog::Level::Debug, false); - let mut pdb: PeerDB = PeerDB::new(vec![], true, &log); + let mut pdb: PeerDB = PeerDB::new(vec![], true); pdb.connect_ingoing(&peer, "/ip4/0.0.0.0".parse().unwrap(), None); diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs index 4cbff59ce2..4c47df6343 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs @@ -4,6 +4,7 @@ use super::sync_status::SyncStatus; use crate::discovery::Eth2Enr; use crate::{rpc::MetaData, types::Subnet}; use discv5::Enr; +use eth2::types::{PeerDirection, PeerState}; use libp2p::core::multiaddr::{Multiaddr, Protocol}; use serde::{ ser::{SerializeStruct, Serializer}, @@ -21,7 +22,7 @@ use PeerConnectionStatus::*; #[serde(bound = "E: EthSpec")] pub struct PeerInfo { /// The peers reputation - score: Score, + pub(crate) score: Score, /// Client managing this peer client: Client, /// Connection status of this peer @@ -50,7 +51,7 @@ pub struct PeerInfo { #[serde(skip)] min_ttl: Option, /// Is the peer a trusted peer. - is_trusted: bool, + pub(crate) is_trusted: bool, /// Direction of the first connection of the last (or current) connected session with this peer. /// None if this peer was never connected. connection_direction: Option, @@ -522,7 +523,7 @@ impl PeerInfo { } /// Connection Direction of connection. -#[derive(Debug, Clone, Serialize, AsRefStr)] +#[derive(Debug, Clone, Copy, Serialize, AsRefStr)] #[strum(serialize_all = "snake_case")] pub enum ConnectionDirection { /// The connection was established by a peer dialing us. @@ -531,6 +532,15 @@ pub enum ConnectionDirection { Outgoing, } +impl From for PeerDirection { + fn from(direction: ConnectionDirection) -> Self { + match direction { + ConnectionDirection::Incoming => PeerDirection::Inbound, + ConnectionDirection::Outgoing => PeerDirection::Outbound, + } + } +} + /// Connection Status of the peer. #[derive(Debug, Clone, Default)] pub enum PeerConnectionStatus { @@ -624,3 +634,14 @@ impl Serialize for PeerConnectionStatus { } } } + +impl From for PeerState { + fn from(status: PeerConnectionStatus) -> Self { + match status { + Connected { .. } => PeerState::Connected, + Dialing { .. } => PeerState::Connecting, + Disconnecting { .. } => PeerState::Disconnecting, + Disconnected { .. } | Banned { .. } | Unknown => PeerState::Disconnected, + } + } +} diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 2bf35b0e35..3adc04eb6a 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -765,8 +765,8 @@ fn handle_rpc_response( SupportedProtocol::PingV1 => Ok(Some(RpcSuccessResponse::Pong(Ping { data: u64::from_ssz_bytes(decoded_buffer)?, }))), - SupportedProtocol::MetaDataV1 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V1( - MetaDataV1::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV1 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V1(MetaDataV1::from_ssz_bytes(decoded_buffer)?), )))), SupportedProtocol::LightClientBootstrapV1 => match fork_name { Some(fork_name) => Ok(Some(RpcSuccessResponse::LightClientBootstrap(Arc::new( @@ -826,11 +826,11 @@ fn handle_rpc_response( )), }, // MetaData V2/V3 responses have no context bytes, so behave similarly to V1 responses - SupportedProtocol::MetaDataV3 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V3( - MetaDataV3::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV3 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V3(MetaDataV3::from_ssz_bytes(decoded_buffer)?), )))), - SupportedProtocol::MetaDataV2 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V2( - MetaDataV2::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV2 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V2(MetaDataV2::from_ssz_bytes(decoded_buffer)?), )))), SupportedProtocol::BlocksByRangeV2 => match fork_name { Some(ForkName::Altair) => Ok(Some(RpcSuccessResponse::BlocksByRange(Arc::new( @@ -1008,6 +1008,7 @@ mod tests { ) -> SignedBeaconBlock { let mut block: BeaconBlockBellatrix<_, FullPayload> = BeaconBlockBellatrix::empty(&Spec::default_spec()); + let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(5000).collect::>()); @@ -1027,6 +1028,7 @@ mod tests { ) -> SignedBeaconBlock { let mut block: BeaconBlockBellatrix<_, FullPayload> = BeaconBlockBellatrix::empty(&Spec::default_spec()); + let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(100000).collect::>()); @@ -1105,28 +1107,31 @@ mod tests { Ping { data: 1 } } - fn metadata() -> MetaData { + fn metadata() -> Arc> { MetaData::V1(MetaDataV1 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), }) + .into() } - fn metadata_v2() -> MetaData { + fn metadata_v2() -> Arc> { MetaData::V2(MetaDataV2 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), syncnets: EnrSyncCommitteeBitfield::::default(), }) + .into() } - fn metadata_v3() -> MetaData { + fn metadata_v3() -> Arc> { MetaData::V3(MetaDataV3 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), syncnets: EnrSyncCommitteeBitfield::::default(), custody_group_count: 1, }) + .into() } /// Encodes the given protocol response as bytes. diff --git a/beacon_node/lighthouse_network/src/rpc/handler.rs b/beacon_node/lighthouse_network/src/rpc/handler.rs index 03203fcade..8353b661c5 100644 --- a/beacon_node/lighthouse_network/src/rpc/handler.rs +++ b/beacon_node/lighthouse_network/src/rpc/handler.rs @@ -15,8 +15,9 @@ use libp2p::swarm::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, }; -use libp2p::swarm::Stream; -use slog::{crit, debug, trace}; +use libp2p::swarm::{ConnectionId, Stream}; +use libp2p::PeerId; +use logging::crit; use smallvec::SmallVec; use std::{ collections::{hash_map::Entry, VecDeque}, @@ -27,6 +28,7 @@ use std::{ }; use tokio::time::{sleep, Sleep}; use tokio_util::time::{delay_queue, DelayQueue}; +use tracing::{debug, trace}; use types::{EthSpec, ForkContext}; /// The number of times to retry an outbound upgrade in the case of IO errors. @@ -135,11 +137,11 @@ where /// Waker, to be sure the handler gets polled when needed. waker: Option, - /// Logger for handling RPC streams - log: slog::Logger, - /// Timeout that will me used for inbound and outbound responses. resp_timeout: Duration, + + /// Information about this handler for logging purposes. + log_info: (PeerId, ConnectionId), } enum HandlerState { @@ -221,8 +223,9 @@ where pub fn new( listen_protocol: SubstreamProtocol, ()>, fork_context: Arc, - log: &slog::Logger, resp_timeout: Duration, + peer_id: PeerId, + connection_id: ConnectionId, ) -> Self { RPCHandler { listen_protocol, @@ -240,8 +243,8 @@ where outbound_io_error_retries: 0, fork_context, waker: None, - log: log.clone(), resp_timeout, + log_info: (peer_id, connection_id), } } @@ -250,7 +253,12 @@ where fn shutdown(&mut self, goodbye_reason: Option<(Id, GoodbyeReason)>) { if matches!(self.state, HandlerState::Active) { if !self.dial_queue.is_empty() { - debug!(self.log, "Starting handler shutdown"; "unsent_queued_requests" => self.dial_queue.len()); + debug!( + unsent_queued_requests = self.dial_queue.len(), + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Starting handler shutdown" + ); } // We now drive to completion communications already dialed/established while let Some((id, req)) = self.dial_queue.pop() { @@ -297,8 +305,10 @@ where let Some(inbound_info) = self.inbound_substreams.get_mut(&inbound_id) else { if !matches!(response, RpcResponse::StreamTermination(..)) { // the stream is closed after sending the expected number of responses - trace!(self.log, "Inbound stream has expired. Response not sent"; - "response" => %response, "id" => inbound_id); + trace!(%response, id = ?inbound_id, + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Inbound stream has expired. Response not sent"); } return; }; @@ -313,8 +323,10 @@ where if matches!(self.state, HandlerState::Deactivated) { // we no longer send responses after the handler is deactivated - debug!(self.log, "Response not sent. Deactivated handler"; - "response" => %response, "id" => inbound_id); + debug!(%response, id = ?inbound_id, + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Response not sent. Deactivated handler"); return; } inbound_info.pending_items.push_back(response); @@ -381,7 +393,11 @@ where match delay.as_mut().poll(cx) { Poll::Ready(_) => { self.state = HandlerState::Deactivated; - debug!(self.log, "Shutdown timeout elapsed, Handler deactivated"); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Shutdown timeout elapsed, Handler deactivated" + ); return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::Close(RPCError::Disconnected), )); @@ -428,7 +444,10 @@ where outbound_err, ))); } else { - crit!(self.log, "timed out substream not in the books"; "stream_id" => outbound_id.get_ref()); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + stream_id = ?outbound_id.get_ref(), "timed out substream not in the books"); } } @@ -557,10 +576,24 @@ where // BlocksByRange is the one that typically consumes the most time. // Its useful to log when the request was completed. if matches!(info.protocol, Protocol::BlocksByRange) { - debug!(self.log, "BlocksByRange Response sent"; "duration" => Instant::now().duration_since(info.request_start_time).as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = Instant::now() + .duration_since(info.request_start_time) + .as_secs(), + "BlocksByRange Response sent" + ); } if matches!(info.protocol, Protocol::BlobsByRange) { - debug!(self.log, "BlobsByRange Response sent"; "duration" => Instant::now().duration_since(info.request_start_time).as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = Instant::now() + .duration_since(info.request_start_time) + .as_secs(), + "BlobsByRange Response sent" + ); } // There is nothing more to process on this substream as it has @@ -583,10 +616,20 @@ where })); if matches!(info.protocol, Protocol::BlocksByRange) { - debug!(self.log, "BlocksByRange Response failed"; "duration" => info.request_start_time.elapsed().as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = info.request_start_time.elapsed().as_secs(), + "BlocksByRange Response failed" + ); } if matches!(info.protocol, Protocol::BlobsByRange) { - debug!(self.log, "BlobsByRange Response failed"; "duration" => info.request_start_time.elapsed().as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = info.request_start_time.elapsed().as_secs(), + "BlobsByRange Response failed" + ); } break; } @@ -695,7 +738,7 @@ where // stream closed // if we expected multiple streams send a stream termination, // else report the stream terminating only. - //trace!(self.log, "RPC Response - stream closed by remote"); + //"RPC Response - stream closed by remote"); // drop the stream let delay_key = &entry.get().delay_key; let request_id = entry.get().req_id; @@ -772,7 +815,11 @@ where } } OutboundSubstreamState::Poisoned => { - crit!(self.log, "Poisoned outbound substream"); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Poisoned outbound substream" + ); unreachable!("Coding Error: Outbound substream is poisoned") } } @@ -804,7 +851,11 @@ where && self.events_out.is_empty() && self.dial_negotiated == 0 { - debug!(self.log, "Goodbye sent, Handler deactivated"); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Goodbye sent, Handler deactivated" + ); self.state = HandlerState::Deactivated; return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::Close(RPCError::Disconnected), @@ -997,7 +1048,11 @@ where ) .is_some() { - crit!(self.log, "Duplicate outbound substream id"; "id" => self.current_outbound_substream_id); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + + id = ?self.current_outbound_substream_id, "Duplicate outbound substream id"); } self.current_outbound_substream_id.0 += 1; } @@ -1045,17 +1100,6 @@ where } } -impl slog::Value for SubstreamId { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::Value::serialize(&self.0, record, key, serializer) - } -} - /// Creates a future that can be polled that will send any queued message to the peer. /// /// This function returns the given substream, along with whether it has been closed or not. Any diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 2f6200a836..b748ab11c0 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -578,7 +578,7 @@ pub enum RpcSuccessResponse { Pong(Ping), /// A response to a META_DATA request. - MetaData(MetaData), + MetaData(Arc>), } /// Indicates which response is being terminated by a stream termination response. @@ -864,19 +864,3 @@ impl std::fmt::Display for DataColumnsByRootRequest { ) } } - -impl slog::KV for StatusMessage { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - serializer.emit_arguments("fork_digest", &format_args!("{:?}", self.fork_digest))?; - Value::serialize(&self.finalized_epoch, record, "finalized_epoch", serializer)?; - serializer.emit_arguments("finalized_root", &format_args!("{}", self.finalized_root))?; - Value::serialize(&self.head_slot, record, "head_slot", serializer)?; - serializer.emit_arguments("head_root", &format_args!("{}", self.head_root))?; - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/rpc/mod.rs b/beacon_node/lighthouse_network/src/rpc/mod.rs index 03f1395b8b..f5085e798c 100644 --- a/beacon_node/lighthouse_network/src/rpc/mod.rs +++ b/beacon_node/lighthouse_network/src/rpc/mod.rs @@ -13,13 +13,14 @@ use libp2p::swarm::{ }; use libp2p::swarm::{ConnectionClosed, FromSwarm, SubstreamProtocol, THandlerInEvent}; use libp2p::PeerId; +use logging::crit; use rate_limiter::{RPCRateLimiter as RateLimiter, RateLimitedErr}; -use slog::{crit, debug, o, trace}; use std::marker::PhantomData; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; +use tracing::{debug, instrument, trace}; use types::{EthSpec, ForkContext}; pub(crate) use handler::{HandlerErr, HandlerEvent}; @@ -159,8 +160,6 @@ pub struct RPC { events: Vec>, fork_context: Arc, enable_light_client_server: bool, - /// Slog logger for RPC behaviour. - log: slog::Logger, /// Networking constant values network_params: NetworkParams, /// A sequential counter indicating when data gets modified. @@ -168,25 +167,28 @@ pub struct RPC { } impl RPC { + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn new( fork_context: Arc, enable_light_client_server: bool, inbound_rate_limiter_config: Option, outbound_rate_limiter_config: Option, - log: slog::Logger, network_params: NetworkParams, seq_number: u64, ) -> Self { - let log = log.new(o!("service" => "libp2p_rpc")); - let inbound_limiter = inbound_rate_limiter_config.map(|config| { - debug!(log, "Using inbound rate limiting params"; "config" => ?config); + debug!(?config, "Using inbound rate limiting params"); RateLimiter::new_with_config(config.0, fork_context.clone()) .expect("Inbound limiter configuration parameters are valid") }); let self_limiter = outbound_rate_limiter_config.map(|config| { - SelfRateLimiter::new(config, fork_context.clone(), log.clone()) + SelfRateLimiter::new(config, fork_context.clone()) .expect("Configuration parameters are valid") }); @@ -196,7 +198,6 @@ impl RPC { events: Vec::new(), fork_context, enable_light_client_server, - log, network_params, seq_number, } @@ -205,6 +206,12 @@ impl RPC { /// Sends an RPC response. /// /// The peer must be connected for this to succeed. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn send_response( &mut self, peer_id: PeerId, @@ -222,6 +229,12 @@ impl RPC { /// Submits an RPC request. /// /// The peer must be connected for this to succeed. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn send_request(&mut self, peer_id: PeerId, request_id: Id, req: RequestType) { let event = if let Some(self_limiter) = self.self_limiter.as_mut() { match self_limiter.allows(peer_id, request_id, req) { @@ -232,18 +245,24 @@ impl RPC { } } } else { - ToSwarm::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - } + RPCSend::Request(request_id, req) }; - self.events.push(event); + self.events.push(BehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::Any, + event, + }); } /// Lighthouse wishes to disconnect from this peer by sending a Goodbye message. This /// gracefully terminates the RPC behaviour with a goodbye message. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn shutdown(&mut self, peer_id: PeerId, id: Id, reason: GoodbyeReason) { self.events.push(ToSwarm::NotifyHandler { peer_id, @@ -252,16 +271,28 @@ impl RPC { }); } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn update_seq_number(&mut self, seq_number: u64) { self.seq_number = seq_number } /// Send a Ping request to the destination `PeerId` via `ConnectionId`. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn ping(&mut self, peer_id: PeerId, id: Id) { let ping = Ping { data: self.seq_number, }; - trace!(self.log, "Sending Ping"; "peer_id" => %peer_id); + trace!(%peer_id, "Sending Ping"); self.send_request(peer_id, id, RequestType::Ping(ping)); } } @@ -291,14 +322,13 @@ where }, (), ); - let log = self - .log - .new(slog::o!("peer_id" => peer_id.to_string(), "connection_id" => connection_id.to_string())); + let handler = RPCHandler::new( protocol, self.fork_context.clone(), - &log, self.network_params.resp_timeout, + peer_id, + connection_id, ); Ok(handler) @@ -323,15 +353,12 @@ where (), ); - let log = self - .log - .new(slog::o!("peer_id" => peer_id.to_string(), "connection_id" => connection_id.to_string())); - let handler = RPCHandler::new( protocol, self.fork_context.clone(), - &log, self.network_params.resp_timeout, + peer_id, + connection_id, ); Ok(handler) @@ -421,10 +448,10 @@ where | Protocol::BlobsByRoot | Protocol::DataColumnsByRoot ) { - debug!(self.log, "Request too large to process"; "request" => %r#type, "protocol" => %protocol); + debug!(request = %r#type, %protocol, "Request too large to process"); } else { // Other protocols shouldn't be sending large messages, we should flag the peer kind - crit!(self.log, "Request size too large to ever be processed"; "protocol" => %protocol); + crit!(%protocol, "Request size too large to ever be processed"); } // send an error code to the peer. // the handler upon receiving the error code will send it back to the behaviour @@ -440,8 +467,7 @@ where return; } Err(RateLimitedErr::TooSoon(wait_time)) => { - debug!(self.log, "Request exceeds the rate limit"; - "request" => %r#type, "peer_id" => %peer_id, "wait_time_ms" => wait_time.as_millis()); + debug!(request = %r#type, %peer_id, wait_time_ms = wait_time.as_millis(), "Request exceeds the rate limit"); // send an error code to the peer. // the handler upon receiving the error code will send it back to the behaviour self.send_response( @@ -462,7 +488,7 @@ where // If we received a Ping, we queue a Pong response. if let RequestType::Ping(_) = r#type { - trace!(self.log, "Received Ping, queueing Pong";"connection_id" => %conn_id, "peer_id" => %peer_id); + trace!(connection_id = %conn_id, %peer_id, "Received Ping, queueing Pong"); self.send_response( peer_id, (conn_id, substream_id), @@ -526,53 +552,3 @@ where Poll::Pending } } - -impl slog::KV for RPCMessage -where - E: EthSpec, - Id: ReqId, -{ - fn serialize( - &self, - _record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - serializer.emit_arguments("peer_id", &format_args!("{}", self.peer_id))?; - match &self.message { - Ok(received) => { - let (msg_kind, protocol) = match received { - RPCReceived::Request(Request { r#type, .. }) => { - ("request", r#type.versioned_protocol().protocol()) - } - RPCReceived::Response(_, res) => ("response", res.protocol()), - RPCReceived::EndOfStream(_, end) => ( - "end_of_stream", - match end { - ResponseTermination::BlocksByRange => Protocol::BlocksByRange, - ResponseTermination::BlocksByRoot => Protocol::BlocksByRoot, - ResponseTermination::BlobsByRange => Protocol::BlobsByRange, - ResponseTermination::BlobsByRoot => Protocol::BlobsByRoot, - ResponseTermination::DataColumnsByRoot => Protocol::DataColumnsByRoot, - ResponseTermination::DataColumnsByRange => Protocol::DataColumnsByRange, - ResponseTermination::LightClientUpdatesByRange => { - Protocol::LightClientUpdatesByRange - } - }, - ), - }; - serializer.emit_str("msg_kind", msg_kind)?; - serializer.emit_arguments("protocol", &format_args!("{}", protocol))?; - } - Err(error) => { - let (msg_kind, protocol) = match &error { - HandlerErr::Inbound { proto, .. } => ("inbound_err", *proto), - HandlerErr::Outbound { proto, .. } => ("outbound_err", *proto), - }; - serializer.emit_str("msg_kind", msg_kind)?; - serializer.emit_arguments("protocol", &format_args!("{}", protocol))?; - } - }; - - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/rpc/self_limiter.rs b/beacon_node/lighthouse_network/src/rpc/self_limiter.rs index ae63e5cdb5..af6ac37d2c 100644 --- a/beacon_node/lighthouse_network/src/rpc/self_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/self_limiter.rs @@ -7,9 +7,10 @@ use std::{ use futures::FutureExt; use libp2p::{swarm::NotifyHandler, PeerId}; -use slog::{crit, debug, Logger}; +use logging::crit; use smallvec::SmallVec; use tokio_util::time::DelayQueue; +use tracing::debug; use types::{EthSpec, ForkContext}; use super::{ @@ -35,9 +36,7 @@ pub(crate) struct SelfRateLimiter { /// Rate limiter for our own requests. limiter: RateLimiter, /// Requests that are ready to be sent. - ready_requests: SmallVec<[BehaviourAction; 3]>, - /// Slog logger. - log: Logger, + ready_requests: SmallVec<[(PeerId, RPCSend); 3]>, } /// Error returned when the rate limiter does not accept a request. @@ -54,9 +53,8 @@ impl SelfRateLimiter { pub fn new( config: OutboundRateLimiterConfig, fork_context: Arc, - log: Logger, ) -> Result { - debug!(log, "Using self rate limiting params"; "config" => ?config); + debug!(?config, "Using self rate limiting params"); let limiter = RateLimiter::new_with_config(config.0, fork_context)?; Ok(SelfRateLimiter { @@ -64,7 +62,6 @@ impl SelfRateLimiter { next_peer_request: Default::default(), limiter, ready_requests: Default::default(), - log, }) } @@ -76,7 +73,7 @@ impl SelfRateLimiter { peer_id: PeerId, request_id: Id, req: RequestType, - ) -> Result, Error> { + ) -> Result, Error> { let protocol = req.versioned_protocol().protocol(); // First check that there are not already other requests waiting to be sent. if let Some(queued_requests) = self.delayed_requests.get_mut(&(peer_id, protocol)) { @@ -84,7 +81,7 @@ impl SelfRateLimiter { return Err(Error::PendingRequests); } - match Self::try_send_request(&mut self.limiter, peer_id, request_id, req, &self.log) { + match Self::try_send_request(&mut self.limiter, peer_id, request_id, req) { Err((rate_limited_req, wait_time)) => { let key = (peer_id, protocol); self.next_peer_request.insert(key, wait_time); @@ -107,14 +104,9 @@ impl SelfRateLimiter { peer_id: PeerId, request_id: Id, req: RequestType, - log: &Logger, - ) -> Result, (QueuedRequest, Duration)> { + ) -> Result, (QueuedRequest, Duration)> { match limiter.allows(&peer_id, &req) { - Ok(()) => Ok(BehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - }), + Ok(()) => Ok(RPCSend::Request(request_id, req)), Err(e) => { let protocol = req.versioned_protocol(); match e { @@ -122,18 +114,13 @@ impl SelfRateLimiter { // this should never happen with default parameters. Let's just send the request. // Log a crit since this is a config issue. crit!( - log, - "Self rate limiting error for a batch that will never fit. Sending request anyway. Check configuration parameters."; - "protocol" => %req.versioned_protocol().protocol() + protocol = %req.versioned_protocol().protocol(), + "Self rate limiting error for a batch that will never fit. Sending request anyway. Check configuration parameters." ); - Ok(BehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - }) + Ok(RPCSend::Request(request_id, req)) } RateLimitedErr::TooSoon(wait_time) => { - debug!(log, "Self rate limiting"; "protocol" => %protocol.protocol(), "wait_time_ms" => wait_time.as_millis(), "peer_id" => %peer_id); + debug!(protocol = %protocol.protocol(), wait_time_ms = wait_time.as_millis(), %peer_id, "Self rate limiting"); Err((QueuedRequest { req, request_id }, wait_time)) } } @@ -147,8 +134,7 @@ impl SelfRateLimiter { if let Entry::Occupied(mut entry) = self.delayed_requests.entry((peer_id, protocol)) { let queued_requests = entry.get_mut(); while let Some(QueuedRequest { req, request_id }) = queued_requests.pop_front() { - match Self::try_send_request(&mut self.limiter, peer_id, request_id, req, &self.log) - { + match Self::try_send_request(&mut self.limiter, peer_id, request_id, req) { Err((rate_limited_req, wait_time)) => { let key = (peer_id, protocol); self.next_peer_request.insert(key, wait_time); @@ -156,7 +142,7 @@ impl SelfRateLimiter { // If one fails just wait for the next window that allows sending requests. return; } - Ok(event) => self.ready_requests.push(event), + Ok(event) => self.ready_requests.push((peer_id, event)), } } if queued_requests.is_empty() { @@ -203,8 +189,12 @@ impl SelfRateLimiter { let _ = self.limiter.poll_unpin(cx); // Finally return any queued events. - if !self.ready_requests.is_empty() { - return Poll::Ready(self.ready_requests.remove(0)); + if let Some((peer_id, event)) = self.ready_requests.pop() { + return Poll::Ready(BehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::Any, + event, + }); } Poll::Pending @@ -219,13 +209,14 @@ mod tests { use crate::rpc::{Ping, Protocol, RequestType}; use crate::service::api_types::{AppRequestId, RequestId, SingleLookupReqId, SyncRequestId}; use libp2p::PeerId; + use logging::create_test_tracing_subscriber; use std::time::Duration; use types::{EthSpec, ForkContext, Hash256, MainnetEthSpec, Slot}; /// Test that `next_peer_request_ready` correctly maintains the queue. #[tokio::test] async fn test_next_peer_request_ready() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let config = OutboundRateLimiterConfig(RateLimiterConfig { ping_quota: Quota::n_every(1, 2), ..Default::default() @@ -236,7 +227,7 @@ mod tests { &MainnetEthSpec::default_spec(), )); let mut limiter: SelfRateLimiter = - SelfRateLimiter::new(config, fork_context, log).unwrap(); + SelfRateLimiter::new(config, fork_context).unwrap(); let peer_id = PeerId::random(); let lookup_id = 0; diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index e69c7aa5f7..894fff5074 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -218,22 +218,6 @@ impl std::convert::From> for RpcResponse { } } -impl slog::Value for RequestId { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - match self { - RequestId::Internal => slog::Value::serialize("Behaviour", record, key, serializer), - RequestId::Application(ref id) => { - slog::Value::serialize(&format_args!("{:?}", id), record, key, serializer) - } - } - } -} - macro_rules! impl_display { ($structname: ty, $format: literal, $($field:ident),*) => { impl Display for $structname { diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 48481c2e1d..9650976c63 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -32,12 +32,13 @@ use libp2p::swarm::behaviour::toggle::Toggle; use libp2p::swarm::{NetworkBehaviour, Swarm, SwarmEvent}; use libp2p::upnp::tokio::Behaviour as Upnp; use libp2p::{identify, PeerId, SwarmBuilder}; -use slog::{crit, debug, info, o, trace, warn}; +use logging::crit; use std::num::{NonZeroU8, NonZeroUsize}; use std::path::PathBuf; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, info, instrument, trace, warn}; use types::{ consts::altair::SYNC_COMMITTEE_SUBNET_COUNT, EnrForkId, EthSpec, ForkContext, Slot, SubnetId, }; @@ -160,23 +161,24 @@ pub struct Network { gossip_cache: GossipCache, /// This node's PeerId. pub local_peer_id: PeerId, - /// Logger for behaviour actions. - log: slog::Logger, } /// Implements the combined behaviour for the libp2p service. impl Network { + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub async fn new( executor: task_executor::TaskExecutor, mut ctx: ServiceContext<'_>, - log: &slog::Logger, ) -> Result<(Self, Arc>), String> { - let log = log.new(o!("service"=> "libp2p")); - let config = ctx.config.clone(); - trace!(log, "Libp2p Service starting"); + trace!("Libp2p Service starting"); // initialise the node's ID - let local_keypair = utils::load_private_key(&config, &log); + let local_keypair = utils::load_private_key(&config); // Trusted peers will also be marked as explicit in GossipSub. // Cfr. https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#explicit-peering-agreements @@ -192,7 +194,6 @@ impl Network { local_keypair.clone(), &config, &ctx.enr_fork_id, - &log, &ctx.chain_spec, )?; @@ -201,15 +202,13 @@ impl Network { ctx.chain_spec .custody_group_count(config.subscribe_all_data_column_subnets) }); - let meta_data = - utils::load_or_build_metadata(&config.network_dir, custody_group_count, &log); + let meta_data = utils::load_or_build_metadata(&config.network_dir, custody_group_count); let seq_number = *meta_data.seq_number(); let globals = NetworkGlobals::new( enr, meta_data, trusted_peers, config.disable_peer_scoring, - &log, config.clone(), ctx.chain_spec.clone(), ); @@ -274,7 +273,7 @@ impl Network { )? }; - trace!(log, "Using peer score params"; "params" => ?params); + trace!(?params, "Using peer score params"); // Set up a scoring update interval let update_gossipsub_scores = tokio::time::interval(params.decay_interval); @@ -374,7 +373,6 @@ impl Network { config.enable_light_client_server, config.inbound_rate_limiter_config.clone(), config.outbound_rate_limiter_config.clone(), - log.clone(), network_params, seq_number, ); @@ -385,7 +383,6 @@ impl Network { local_keypair.clone(), &config, network_globals.clone(), - &log, &ctx.chain_spec, ) .await?; @@ -418,7 +415,7 @@ impl Network { target_peer_count: config.target_peers, ..Default::default() }; - PeerManager::new(peer_manager_cfg, network_globals.clone(), &log)? + PeerManager::new(peer_manager_cfg, network_globals.clone())? }; let connection_limits = { @@ -513,7 +510,6 @@ impl Network { update_gossipsub_scores, gossip_cache, local_peer_id, - log, }; network.start(&config).await?; @@ -528,10 +524,25 @@ impl Network { /// - Starts listening in the given ports. /// - Dials boot-nodes and libp2p peers. /// - Subscribes to starting gossipsub topics. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] async fn start(&mut self, config: &crate::NetworkConfig) -> Result<(), String> { let enr = self.network_globals.local_enr(); - info!(self.log, "Libp2p Starting"; "peer_id" => %enr.peer_id(), "bandwidth_config" => format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name)); - debug!(self.log, "Attempting to open listening ports"; config.listen_addrs(), "discovery_enabled" => !config.disable_discovery, "quic_enabled" => !config.disable_quic_support); + info!( + peer_id = %enr.peer_id(), + bandwidth_config = format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name), + "Libp2p Starting" + ); + debug!( + listen_addrs = ?config.listen_addrs(), + discovery_enabled = !config.disable_discovery, + quic_enabled = !config.disable_quic_support, + "Attempting to open listening ports" + ); for listen_multiaddr in config.listen_addrs().libp2p_addresses() { // If QUIC is disabled, ignore listening on QUIC ports @@ -545,14 +556,13 @@ impl Network { Ok(_) => { let mut log_address = listen_multiaddr; log_address.push(MProtocol::P2p(enr.peer_id())); - info!(self.log, "Listening established"; "address" => %log_address); + info!(address = %log_address, "Listening established"); } Err(err) => { crit!( - self.log, - "Unable to listen on libp2p address"; - "error" => ?err, - "listen_multiaddr" => %listen_multiaddr, + error = ?err, + %listen_multiaddr, + "Unable to listen on libp2p address" ); return Err("Libp2p was unable to listen on the given listen address.".into()); } @@ -564,9 +574,9 @@ impl Network { // strip the p2p protocol if it exists strip_peer_id(&mut multiaddr); match self.swarm.dial(multiaddr.clone()) { - Ok(()) => debug!(self.log, "Dialing libp2p peer"; "address" => %multiaddr), + Ok(()) => debug!(address = %multiaddr, "Dialing libp2p peer"), Err(err) => { - debug!(self.log, "Could not connect to peer"; "address" => %multiaddr, "error" => ?err) + debug!(address = %multiaddr, error = ?err, "Could not connect to peer") } }; }; @@ -629,12 +639,12 @@ impl Network { if self.subscribe_kind(topic_kind.clone()) { subscribed_topics.push(topic_kind.clone()); } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic_kind); + warn!(topic = %topic_kind, "Could not subscribe to topic"); } } if !subscribed_topics.is_empty() { - info!(self.log, "Subscribed to topics"; "topics" => ?subscribed_topics); + info!(topics = ?subscribed_topics, "Subscribed to topics"); } Ok(()) @@ -643,48 +653,114 @@ impl Network { /* Public Accessible Functions to interact with the behaviour */ /// The routing pub-sub mechanism for eth2. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn gossipsub_mut(&mut self) -> &mut Gossipsub { &mut self.swarm.behaviour_mut().gossipsub } /// The Eth2 RPC specified in the wire-0 protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn eth2_rpc_mut(&mut self) -> &mut RPC { &mut self.swarm.behaviour_mut().eth2_rpc } /// Discv5 Discovery protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discovery_mut(&mut self) -> &mut Discovery { &mut self.swarm.behaviour_mut().discovery } /// Provides IP addresses and peer information. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn identify_mut(&mut self) -> &mut identify::Behaviour { &mut self.swarm.behaviour_mut().identify } /// The peer manager that keeps track of peer's reputation and status. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn peer_manager_mut(&mut self) -> &mut PeerManager { &mut self.swarm.behaviour_mut().peer_manager } /// The routing pub-sub mechanism for eth2. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn gossipsub(&self) -> &Gossipsub { &self.swarm.behaviour().gossipsub } /// The Eth2 RPC specified in the wire-0 protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn eth2_rpc(&self) -> &RPC { &self.swarm.behaviour().eth2_rpc } /// Discv5 Discovery protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discovery(&self) -> &Discovery { &self.swarm.behaviour().discovery } /// Provides IP addresses and peer information. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn identify(&self) -> &identify::Behaviour { &self.swarm.behaviour().identify } /// The peer manager that keeps track of peer's reputation and status. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn peer_manager(&self) -> &PeerManager { &self.swarm.behaviour().peer_manager } /// Returns the local ENR of the node. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn local_enr(&self) -> Enr { self.network_globals.local_enr() } @@ -693,6 +769,12 @@ impl Network { /// Subscribes to a gossipsub topic kind, letting the network service determine the /// encoding and fork version. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe_kind(&mut self, kind: GossipKind) -> bool { let gossip_topic = GossipTopic::new( kind, @@ -705,6 +787,12 @@ impl Network { /// Unsubscribes from a gossipsub topic kind, letting the network service determine the /// encoding and fork version. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe_kind(&mut self, kind: GossipKind) -> bool { let gossip_topic = GossipTopic::new( kind, @@ -715,6 +803,12 @@ impl Network { } /// Subscribe to all required topics for the `new_fork` with the given `new_fork_digest`. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe_new_fork_topics(&mut self, new_fork: ForkName, new_fork_digest: [u8; 4]) { // Re-subscribe to non-core topics with the new fork digest let subscriptions = self.network_globals.gossipsub_subscriptions.read().clone(); @@ -739,6 +833,12 @@ impl Network { } /// Unsubscribe from all topics that doesn't have the given fork_digest + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe_from_fork_topics_except(&mut self, except: [u8; 4]) { let subscriptions = self.network_globals.gossipsub_subscriptions.read().clone(); for topic in subscriptions @@ -751,6 +851,12 @@ impl Network { } /// Remove topic weight from all topics that don't have the given fork digest. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn remove_topic_weight_except(&mut self, except: [u8; 4]) { let new_param = TopicScoreParams { topic_weight: 0.0, @@ -766,15 +872,21 @@ impl Network { .gossipsub_mut() .set_topic_params(libp2p_topic, new_param.clone()) { - Ok(_) => debug!(self.log, "Removed topic weight"; "topic" => %topic), + Ok(_) => debug!(%topic, "Removed topic weight"), Err(e) => { - warn!(self.log, "Failed to remove topic weight"; "topic" => %topic, "error" => e) + warn!(%topic, error = e, "Failed to remove topic weight") } } } } /// Returns the scoring parameters for a topic if set. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn get_topic_params(&self, topic: GossipTopic) -> Option<&TopicScoreParams> { self.swarm .behaviour() @@ -785,6 +897,12 @@ impl Network { /// Subscribes to a gossipsub topic. /// /// Returns `true` if the subscription was successful and `false` otherwise. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe(&mut self, topic: GossipTopic) -> bool { // update the network globals self.network_globals @@ -796,17 +914,23 @@ impl Network { match self.gossipsub_mut().subscribe(&topic) { Err(e) => { - warn!(self.log, "Failed to subscribe to topic"; "topic" => %topic, "error" => ?e); + warn!(%topic, error = ?e, "Failed to subscribe to topic"); false } Ok(_) => { - debug!(self.log, "Subscribed to topic"; "topic" => %topic); + debug!(%topic, "Subscribed to topic"); true } } } /// Unsubscribe from a gossipsub topic. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe(&mut self, topic: GossipTopic) -> bool { // update the network globals self.network_globals @@ -817,20 +941,17 @@ impl Network { // unsubscribe from the topic let libp2p_topic: Topic = topic.clone().into(); - match self.gossipsub_mut().unsubscribe(&libp2p_topic) { - Err(_) => { - warn!(self.log, "Failed to unsubscribe from topic"; "topic" => %libp2p_topic); - false - } - Ok(v) => { - // Inform the network - debug!(self.log, "Unsubscribed to topic"; "topic" => %topic); - v - } - } + debug!(%topic, "Unsubscribed to topic"); + self.gossipsub_mut().unsubscribe(&libp2p_topic) } /// Publishes a list of messages on the pubsub (gossipsub) behaviour, choosing the encoding. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn publish(&mut self, messages: Vec>) { for message in messages { for topic in message.topics(GossipEncoding::default(), self.enr_fork_id.fork_digest) { @@ -842,17 +963,15 @@ impl Network { match e { PublishError::Duplicate => { debug!( - self.log, - "Attempted to publish duplicate message"; - "kind" => %topic.kind(), + kind = %topic.kind(), + "Attempted to publish duplicate message" ); } ref e => { warn!( - self.log, - "Could not publish message"; - "error" => ?e, - "kind" => %topic.kind(), + error = ?e, + kind = %topic.kind(), + "Could not publish message" ); } } @@ -877,7 +996,7 @@ impl Network { } } - if let PublishError::InsufficientPeers = e { + if let PublishError::NoPeersSubscribedToTopic = e { self.gossip_cache.insert(topic, message_data); } } @@ -887,6 +1006,12 @@ impl Network { /// Informs the gossipsub about the result of a message validation. /// If the message is valid it will get propagated by gossipsub. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn report_message_validation_result( &mut self, propagation_source: &PeerId, @@ -912,17 +1037,21 @@ impl Network { } } - if let Err(e) = self.gossipsub_mut().report_message_validation_result( + self.gossipsub_mut().report_message_validation_result( &message_id, propagation_source, validation_result, - ) { - warn!(self.log, "Failed to report message validation"; "message_id" => %message_id, "peer_id" => %propagation_source, "error" => ?e); - } + ); } /// Updates the current gossipsub scoring parameters based on the validator count and current /// slot. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_gossipsub_parameters( &mut self, active_validators: usize, @@ -937,12 +1066,12 @@ impl Network { GossipTopic::new(kind, GossipEncoding::default(), fork_digest).into() }; - debug!(self.log, "Updating gossipsub score parameters"; - "active_validators" => active_validators); - trace!(self.log, "Updated gossipsub score parameters"; - "beacon_block_params" => ?beacon_block_params, - "beacon_aggregate_proof_params" => ?beacon_aggregate_proof_params, - "beacon_attestation_subnet_params" => ?beacon_attestation_subnet_params, + debug!(active_validators, "Updating gossipsub score parameters"); + trace!( + ?beacon_block_params, + ?beacon_aggregate_proof_params, + ?beacon_attestation_subnet_params, + "Updated gossipsub score parameters" ); self.gossipsub_mut() @@ -966,6 +1095,12 @@ impl Network { /* Eth2 RPC behaviour functions */ /// Send a request to a peer over RPC. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_request( &mut self, peer_id: PeerId, @@ -983,6 +1118,12 @@ impl Network { } /// Send a successful response to a peer over RPC. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_response( &mut self, peer_id: PeerId, @@ -995,6 +1136,12 @@ impl Network { } /// Inform the peer that their request produced an error. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_error_response( &mut self, peer_id: PeerId, @@ -1012,11 +1159,22 @@ impl Network { } /* Peer management functions */ - + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn testing_dial(&mut self, addr: Multiaddr) -> Result<(), libp2p::swarm::DialError> { self.swarm.dial(addr) } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn report_peer( &mut self, peer_id: &PeerId, @@ -1032,6 +1190,12 @@ impl Network { /// /// This will send a goodbye, disconnect and then ban the peer. /// This is fatal for a peer, and should be used in unrecoverable circumstances. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn goodbye_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason, source: ReportSource) { self.peer_manager_mut() .goodbye_peer(peer_id, reason, source); @@ -1039,16 +1203,34 @@ impl Network { /// Hard (ungraceful) disconnect for testing purposes only /// Use goodbye_peer for disconnections, do not use this function. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn __hard_disconnect_testing_only(&mut self, peer_id: PeerId) { let _ = self.swarm.disconnect_peer_id(peer_id); } /// Returns an iterator over all enr entries in the DHT. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn enr_entries(&self) -> Vec { self.discovery().table_entries_enr() } /// Add an ENR to the routing table of the discovery mechanism. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn add_enr(&mut self, enr: Enr) { self.discovery_mut().add_enr(enr); } @@ -1056,9 +1238,15 @@ impl Network { /// Updates a subnet value to the ENR attnets/syncnets bitfield. /// /// The `value` is `true` if a subnet is being added and false otherwise. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_enr_subnet(&mut self, subnet_id: Subnet, value: bool) { if let Err(e) = self.discovery_mut().update_enr_bitfield(subnet_id, value) { - crit!(self.log, "Could not update ENR bitfield"; "error" => e); + crit!(error = e, "Could not update ENR bitfield"); } // update the local meta data which informs our peers of the update during PINGS self.update_metadata_bitfields(); @@ -1066,6 +1254,12 @@ impl Network { /// Attempts to discover new peers for a given subnet. The `min_ttl` gives the time at which we /// would like to retain the peers for. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discover_subnet_peers(&mut self, subnets_to_discover: Vec) { // If discovery is not started or disabled, ignore the request if !self.discovery().started { @@ -1096,12 +1290,11 @@ impl Network { .count(); if peers_on_subnet >= TARGET_SUBNET_PEERS { trace!( - self.log, - "Discovery query ignored"; - "subnet" => ?s.subnet, - "reason" => "Already connected to desired peers", - "connected_peers_on_subnet" => peers_on_subnet, - "target_subnet_peers" => TARGET_SUBNET_PEERS, + subnet = ?s.subnet, + reason = "Already connected to desired peers", + connected_peers_on_subnet = peers_on_subnet, + target_subnet_peers = TARGET_SUBNET_PEERS, + "Discovery query ignored" ); false // Queue an outgoing connection request to the cached peers that are on `s.subnet_id`. @@ -1121,6 +1314,12 @@ impl Network { } /// Updates the local ENR's "eth2" field with the latest EnrForkId. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_fork_version(&mut self, enr_fork_id: EnrForkId) { self.discovery_mut().update_eth2_enr(enr_fork_id.clone()); @@ -1131,6 +1330,12 @@ impl Network { /* Private internal functions */ /// Updates the current meta data of the node to match the local ENR. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn update_metadata_bitfields(&mut self) { let local_attnets = self .discovery_mut() @@ -1158,15 +1363,27 @@ impl Network { drop(meta_data_w); self.eth2_rpc_mut().update_seq_number(seq_number); // Save the updated metadata to disk - utils::save_metadata_to_disk(&self.network_dir, meta_data, &self.log); + utils::save_metadata_to_disk(&self.network_dir, meta_data); } /// Sends a Ping request to the peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn ping(&mut self, peer_id: PeerId) { self.eth2_rpc_mut().ping(peer_id, RequestId::Internal); } /// Sends a METADATA request to a peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn send_meta_data_request(&mut self, peer_id: PeerId) { let event = if self.fork_context.spec.is_peer_das_scheduled() { // Nodes with higher custody will probably start advertising it @@ -1181,6 +1398,12 @@ impl Network { } /// Sends a METADATA response to a peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn send_meta_data_response( &mut self, _req: MetadataRequest, @@ -1190,7 +1413,7 @@ impl Network { ) { let metadata = self.network_globals.local_metadata.read().clone(); // The encoder is responsible for sending the negotiated version of the metadata - let event = RpcResponse::Success(RpcSuccessResponse::MetaData(metadata)); + let event = RpcResponse::Success(RpcSuccessResponse::MetaData(Arc::new(metadata))); self.eth2_rpc_mut() .send_response(peer_id, id, request_id, event); } @@ -1198,6 +1421,12 @@ impl Network { // RPC Propagation methods /// Queues the response to be sent upwards as long at it was requested outside the Behaviour. #[must_use = "return the response"] + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn build_response( &mut self, id: RequestId, @@ -1216,8 +1445,14 @@ impl Network { /// Dial cached Enrs in discovery service that are in the given `subnet_id` and aren't /// in Connected, Dialing or Banned state. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn dial_cached_enrs_in_subnet(&mut self, subnet: Subnet, spec: Arc) { - let predicate = subnet_predicate::(vec![subnet], &self.log, spec); + let predicate = subnet_predicate::(vec![subnet], spec); let peers_to_dial: Vec = self .discovery() .cached_enrs() @@ -1235,14 +1470,35 @@ impl Network { self.discovery_mut().remove_cached_enr(&enr.peer_id()); let peer_id = enr.peer_id(); if self.peer_manager_mut().dial_peer(enr) { - debug!(self.log, "Added cached ENR peer to dial queue"; "peer_id" => %peer_id); + debug!(%peer_id, "Added cached ENR peer to dial queue"); } } } + /// Adds the given `enr` to the trusted peers mapping and tries to dial it + /// every heartbeat to maintain the connection. + pub fn dial_trusted_peer(&mut self, enr: Enr) { + self.peer_manager_mut().add_trusted_peer(enr.clone()); + self.peer_manager_mut().dial_peer(enr); + } + + /// Remove the given peer from the trusted peers mapping if it exists and disconnect + /// from it. + pub fn remove_trusted_peer(&mut self, enr: Enr) { + self.peer_manager_mut().remove_trusted_peer(enr.clone()); + self.peer_manager_mut() + .disconnect_peer(enr.peer_id(), GoodbyeReason::TooManyPeers); + } + /* Sub-behaviour event handling functions */ /// Handle a gossipsub event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_gs_event(&mut self, event: gossipsub::Event) -> Option> { match event { gossipsub::Event::Message { @@ -1254,15 +1510,13 @@ impl Network { // peer that originally published the message. match PubsubMessage::decode(&gs_msg.topic, &gs_msg.data, &self.fork_context) { Err(e) => { - debug!(self.log, "Could not decode gossipsub message"; "topic" => ?gs_msg.topic,"error" => e); + debug!(topic = ?gs_msg.topic, error = e, "Could not decode gossipsub message"); //reject the message - if let Err(e) = self.gossipsub_mut().report_message_validation_result( + self.gossipsub_mut().report_message_validation_result( &id, &propagation_source, MessageAcceptance::Reject, - ) { - warn!(self.log, "Failed to report message validation"; "message_id" => %id, "peer_id" => %propagation_source, "error" => ?e); - } + ); } Ok(msg) => { // Notify the network @@ -1294,11 +1548,7 @@ impl Network { .publish(Topic::from(topic.clone()), data) { Ok(_) => { - debug!( - self.log, - "Gossip message published on retry"; - "topic" => topic_str - ); + debug!(topic = topic_str, "Gossip message published on retry"); metrics::inc_counter_vec( &metrics::GOSSIP_LATE_PUBLISH_PER_TOPIC_KIND, &[topic_str], @@ -1306,10 +1556,9 @@ impl Network { } Err(PublishError::Duplicate) => { debug!( - self.log, - "Gossip message publish ignored on retry"; - "reason" => "duplicate", - "topic" => topic_str + reason = "duplicate", + topic = topic_str, + "Gossip message publish ignored on retry" ); metrics::inc_counter_vec( &metrics::GOSSIP_FAILED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1318,10 +1567,9 @@ impl Network { } Err(e) => { warn!( - self.log, - "Gossip message publish failed on retry"; - "topic" => topic_str, - "error" => %e + topic = topic_str, + error = %e, + "Gossip message publish failed on retry" ); metrics::inc_counter_vec( &metrics::GOSSIP_FAILED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1342,7 +1590,7 @@ impl Network { } } gossipsub::Event::GossipsubNotSupported { peer_id } => { - debug!(self.log, "Peer does not support gossipsub"; "peer_id" => %peer_id); + debug!(%peer_id, "Peer does not support gossipsub"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::Fatal, @@ -1355,10 +1603,17 @@ impl Network { peer_id, failed_messages, } => { - debug!(self.log, "Slow gossipsub peer"; "peer_id" => %peer_id, "publish" => failed_messages.publish, "forward" => failed_messages.forward, "priority" => failed_messages.priority, "non_priority" => failed_messages.non_priority); + debug!( + peer_id = %peer_id, + publish = failed_messages.publish, + forward = failed_messages.forward, + priority = failed_messages.priority, + non_priority = failed_messages.non_priority, + "Slow gossipsub peer" + ); // Punish the peer if it cannot handle priority messages - if failed_messages.total_timeout() > 10 { - debug!(self.log, "Slow gossipsub peer penalized for priority failure"; "peer_id" => %peer_id); + if failed_messages.timeout > 10 { + debug!(%peer_id, "Slow gossipsub peer penalized for priority failure"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::HighToleranceError, @@ -1367,7 +1622,7 @@ impl Network { "publish_timeout_penalty", ); } else if failed_messages.total_queue_full() > 10 { - debug!(self.log, "Slow gossipsub peer penalized for send queue full"; "peer_id" => %peer_id); + debug!(%peer_id, "Slow gossipsub peer penalized for send queue full"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::HighToleranceError, @@ -1382,6 +1637,12 @@ impl Network { } /// Handle an RPC event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_rpc_event(&mut self, event: RPCMessage) -> Option> { let peer_id = event.peer_id; @@ -1391,11 +1652,7 @@ impl Network { && (matches!(event.message, Err(HandlerErr::Inbound { .. })) || matches!(event.message, Ok(RPCReceived::Request(..)))) { - debug!( - self.log, - "Ignoring rpc message of disconnecting peer"; - event - ); + debug!(?event, "Ignoring rpc message of disconnecting peer"); return None; } @@ -1458,10 +1715,10 @@ impl Network { RequestType::Goodbye(reason) => { // queue for disconnection without a goodbye message debug!( - self.log, "Peer sent Goodbye"; - "peer_id" => %peer_id, - "reason" => %reason, - "client" => %self.network_globals.client(&peer_id), + %peer_id, + %reason, + client = %self.network_globals.client(&peer_id), + "Peer sent Goodbye" ); // NOTE: We currently do not inform the application that we are // disconnecting here. The RPC handler will automatically @@ -1605,7 +1862,7 @@ impl Network { } RpcSuccessResponse::MetaData(meta_data) => { self.peer_manager_mut() - .meta_data_response(&peer_id, meta_data); + .meta_data_response(&peer_id, meta_data.as_ref().clone()); None } /* Network propagated protocols */ @@ -1672,6 +1929,12 @@ impl Network { } /// Handle an identify event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_identify_event(&mut self, event: identify::Event) -> Option> { match event { identify::Event::Received { @@ -1680,10 +1943,7 @@ impl Network { connection_id: _, } => { if info.listen_addrs.len() > MAX_IDENTIFY_ADDRESSES { - debug!( - self.log, - "More than 10 addresses have been identified, truncating" - ); + debug!("More than 10 addresses have been identified, truncating"); info.listen_addrs.truncate(MAX_IDENTIFY_ADDRESSES); } // send peer info to the peer manager. @@ -1697,6 +1957,12 @@ impl Network { } /// Handle a peer manager event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_pm_event(&mut self, event: PeerManagerEvent) -> Option> { match event { PeerManagerEvent::PeerConnectedIncoming(peer_id) => { @@ -1741,8 +2007,7 @@ impl Network { None } PeerManagerEvent::DisconnectPeer(peer_id, reason) => { - debug!(self.log, "Peer Manager disconnecting peer"; - "peer_id" => %peer_id, "reason" => %reason); + debug!(%peer_id, %reason, "Peer Manager disconnecting peer"); // send one goodbye self.eth2_rpc_mut() .shutdown(peer_id, RequestId::Internal, reason); @@ -1751,10 +2016,16 @@ impl Network { } } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_upnp_event(&mut self, event: libp2p::upnp::Event) { match event { libp2p::upnp::Event::NewExternalAddr(addr) => { - info!(self.log, "UPnP route established"; "addr" => %addr); + info!(%addr, "UPnP route established"); let mut iter = addr.iter(); let is_ip6 = { let addr = iter.next(); @@ -1766,38 +2037,40 @@ impl Network { if let Err(e) = self.discovery_mut().update_enr_quic_port(udp_port, is_ip6) { - warn!(self.log, "Failed to update ENR"; "error" => e); + warn!(error = e, "Failed to update ENR"); } } _ => { - trace!(self.log, "UPnP address mapped multiaddr from unknown transport"; "addr" => %addr) + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); } }, Some(multiaddr::Protocol::Tcp(tcp_port)) => { if let Err(e) = self.discovery_mut().update_enr_tcp_port(tcp_port, is_ip6) { - warn!(self.log, "Failed to update ENR"; "error" => e); + warn!(error = e, "Failed to update ENR"); } } _ => { - trace!(self.log, "UPnP address mapped multiaddr from unknown transport"; "addr" => %addr); + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); } } } libp2p::upnp::Event::ExpiredExternalAddr(_) => {} libp2p::upnp::Event::GatewayNotFound => { - info!(self.log, "UPnP not available"); + info!("UPnP not available"); } libp2p::upnp::Event::NonRoutableGateway => { - info!( - self.log, - "UPnP is available but gateway is not exposed to public network" - ); + info!("UPnP is available but gateway is not exposed to public network"); } } } /* Networking polling */ - + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub async fn next_event(&mut self) -> NetworkEvent { loop { tokio::select! { @@ -1808,7 +2081,6 @@ impl Network { return event; } }, - // perform gossipsub score updates when necessary _ = self.update_gossipsub_scores.tick() => { let this = self.swarm.behaviour_mut(); @@ -1817,7 +2089,7 @@ impl Network { // poll the gossipsub cache to clear expired messages Some(result) = self.gossip_cache.next() => { match result { - Err(e) => warn!(self.log, "Gossip cache error"; "error" => e), + Err(e) => warn!(error = e, "Gossip cache error"), Ok(expired_topic) => { if let Some(v) = metrics::get_int_counter( &metrics::GOSSIP_EXPIRED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1832,6 +2104,12 @@ impl Network { } } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn parse_swarm_event( &mut self, event: SwarmEvent>, @@ -1865,7 +2143,7 @@ impl Network { send_back_addr, connection_id: _, } => { - trace!(self.log, "Incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr); + trace!(our_addr = %local_addr, from = %send_back_addr, "Incoming connection"); None } SwarmEvent::IncomingConnectionError { @@ -1896,7 +2174,7 @@ impl Network { } }, }; - debug!(self.log, "Failed incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr, "error" => error_repr); + debug!(our_addr = %local_addr, from = %send_back_addr, error = error_repr, "Failed incoming connection"); None } SwarmEvent::OutgoingConnectionError { @@ -1911,7 +2189,7 @@ impl Network { } SwarmEvent::NewListenAddr { address, .. } => Some(NetworkEvent::NewListenAddr(address)), SwarmEvent::ExpiredListenAddr { address, .. } => { - debug!(self.log, "Listen address expired"; "address" => %address); + debug!(%address, "Listen address expired"); None } SwarmEvent::ListenerClosed { @@ -1919,10 +2197,10 @@ impl Network { } => { match reason { Ok(_) => { - debug!(self.log, "Listener gracefully closed"; "addresses" => ?addresses) + debug!(?addresses, "Listener gracefully closed") } Err(reason) => { - crit!(self.log, "Listener abruptly closed"; "addresses" => ?addresses, "reason" => ?reason) + crit!(?addresses, ?reason, "Listener abruptly closed") } }; if Swarm::listeners(&self.swarm).count() == 0 { @@ -1932,7 +2210,7 @@ impl Network { } } SwarmEvent::ListenerError { error, .. } => { - debug!(self.log, "Listener closed connection attempt"; "reason" => ?error); + debug!(reason = ?error, "Listener closed connection attempt"); None } _ => { diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index 72c2b29102..01929bcb01 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -9,7 +9,6 @@ use libp2p::core::{multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxe use libp2p::identity::{secp256k1, Keypair}; use libp2p::{core, noise, yamux, PeerId, Transport}; use prometheus_client::registry::Registry; -use slog::{debug, warn}; use ssz::Decode; use std::collections::HashSet; use std::fs::File; @@ -17,6 +16,7 @@ use std::io::prelude::*; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, warn}; use types::{ ChainSpec, DataColumnSubnetId, EnrForkId, EthSpec, ForkContext, SubnetId, SyncSubnetId, }; @@ -107,21 +107,21 @@ fn keypair_from_bytes(mut bytes: Vec) -> Result { /// generated and is then saved to disk. /// /// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5. -pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair { +pub fn load_private_key(config: &NetworkConfig) -> Keypair { // check for key from disk let network_key_f = config.network_dir.join(NETWORK_KEY_FILENAME); if let Ok(mut network_key_file) = File::open(network_key_f.clone()) { let mut key_bytes: Vec = Vec::with_capacity(36); match network_key_file.read_to_end(&mut key_bytes) { - Err(_) => debug!(log, "Could not read network key file"), + Err(_) => debug!("Could not read network key file"), Ok(_) => { // only accept secp256k1 keys for now if let Ok(secret_key) = secp256k1::SecretKey::try_from_bytes(&mut key_bytes) { let kp: secp256k1::Keypair = secret_key.into(); - debug!(log, "Loaded network key from disk."); + debug!("Loaded network key from disk."); return kp.into(); } else { - debug!(log, "Network key file is not a valid secp256k1 key"); + debug!("Network key file is not a valid secp256k1 key"); } } } @@ -134,12 +134,12 @@ pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair { .and_then(|mut f| f.write_all(&local_private_key.secret().to_bytes())) { Ok(_) => { - debug!(log, "New network key generated and written to disk"); + debug!("New network key generated and written to disk"); } Err(e) => { warn!( - log, - "Could not write node key to file: {:?}. error: {}", network_key_f, e + "Could not write node key to file: {:?}. error: {}", + network_key_f, e ); } } @@ -166,7 +166,6 @@ pub fn strip_peer_id(addr: &mut Multiaddr) { pub fn load_or_build_metadata( network_dir: &Path, custody_group_count_opt: Option, - log: &slog::Logger, ) -> MetaData { // We load a V2 metadata version by default (regardless of current fork) // since a V2 metadata can be converted to V1. The RPC encoder is responsible @@ -192,7 +191,7 @@ pub fn load_or_build_metadata( { meta_data.seq_number += 1; } - debug!(log, "Loaded metadata from disk"); + debug!("Loaded metadata from disk"); } Err(_) => { match MetaDataV1::::from_ssz_bytes(&metadata_ssz) { @@ -200,13 +199,12 @@ pub fn load_or_build_metadata( let persisted_metadata = MetaData::V1(persisted_metadata); // Increment seq number as the persisted metadata version is updated meta_data.seq_number = *persisted_metadata.seq_number() + 1; - debug!(log, "Loaded metadata from disk"); + debug!("Loaded metadata from disk"); } Err(e) => { debug!( - log, - "Metadata from file could not be decoded"; - "error" => ?e, + error = ?e, + "Metadata from file could not be decoded" ); } } @@ -227,8 +225,8 @@ pub fn load_or_build_metadata( MetaData::V2(meta_data) }; - debug!(log, "Metadata sequence number"; "seq_num" => meta_data.seq_number()); - save_metadata_to_disk(network_dir, meta_data.clone(), log); + debug!(seq_num = meta_data.seq_number(), "Metadata sequence number"); + save_metadata_to_disk(network_dir, meta_data.clone()); meta_data } @@ -275,11 +273,7 @@ pub(crate) fn create_whitelist_filter( } /// Persist metadata to disk -pub(crate) fn save_metadata_to_disk( - dir: &Path, - metadata: MetaData, - log: &slog::Logger, -) { +pub(crate) fn save_metadata_to_disk(dir: &Path, metadata: MetaData) { let _ = std::fs::create_dir_all(dir); // We always store the metadata v2 to disk because // custody_group_count parameter doesn't need to be persisted across runs. @@ -288,14 +282,13 @@ pub(crate) fn save_metadata_to_disk( let metadata_bytes = metadata.metadata_v2().as_ssz_bytes(); match File::create(dir.join(METADATA_FILENAME)).and_then(|mut f| f.write_all(&metadata_bytes)) { Ok(_) => { - debug!(log, "Metadata written to disk"); + debug!("Metadata written to disk"); } Err(e) => { warn!( - log, - "Could not write metadata to disk"; - "file" => format!("{:?}{:?}", dir, METADATA_FILENAME), - "error" => %e + file = format!("{:?}{:?}", dir, METADATA_FILENAME), + error = %e, + "Could not write metadata to disk" ); } } diff --git a/beacon_node/lighthouse_network/src/types/globals.rs b/beacon_node/lighthouse_network/src/types/globals.rs index d243c68c0f..3031a0dff7 100644 --- a/beacon_node/lighthouse_network/src/types/globals.rs +++ b/beacon_node/lighthouse_network/src/types/globals.rs @@ -5,9 +5,9 @@ use crate::rpc::{MetaData, MetaDataV3}; use crate::types::{BackFillState, SyncState}; use crate::{Client, Enr, EnrExt, GossipTopic, Multiaddr, NetworkConfig, PeerId}; use parking_lot::RwLock; -use slog::error; use std::collections::HashSet; use std::sync::Arc; +use tracing::error; use types::data_column_custody_group::{ compute_columns_for_custody_group, compute_subnets_from_custody_group, get_custody_groups, }; @@ -33,6 +33,8 @@ pub struct NetworkGlobals { /// The computed sampling subnets and columns is stored to avoid re-computing. pub sampling_subnets: HashSet, pub sampling_columns: HashSet, + /// Constant custody group count (CGC) set at startup + custody_group_count: u64, /// Network-related configuration. Immutable after initialization. pub config: Arc, /// Ethereum chain configuration. Immutable after initialization. @@ -45,63 +47,58 @@ impl NetworkGlobals { local_metadata: MetaData, trusted_peers: Vec, disable_peer_scoring: bool, - log: &slog::Logger, config: Arc, spec: Arc, ) -> Self { - let (sampling_subnets, sampling_columns) = if spec.is_peer_das_scheduled() { - let node_id = enr.node_id().raw(); + let node_id = enr.node_id().raw(); - let custody_group_count = match local_metadata.custody_group_count() { - Ok(&cgc) if cgc <= spec.number_of_custody_groups => cgc, - _ => { + let custody_group_count = match local_metadata.custody_group_count() { + Ok(&cgc) if cgc <= spec.number_of_custody_groups => cgc, + _ => { + if spec.is_peer_das_scheduled() { error!( - log, - "custody_group_count from metadata is either invalid or not set. This is a bug!"; - "info" => "falling back to default custody requirement" + info = "falling back to default custody requirement", + "custody_group_count from metadata is either invalid or not set. This is a bug!" ); - spec.custody_requirement } - }; - - // The below `expect` calls will panic on start up if the chain spec config values used - // are invalid - let sampling_size = spec - .sampling_size(custody_group_count) - .expect("should compute node sampling size from valid chain spec"); - let custody_groups = get_custody_groups(node_id, sampling_size, &spec) - .expect("should compute node custody groups"); - - let mut sampling_subnets = HashSet::new(); - for custody_index in &custody_groups { - let subnets = compute_subnets_from_custody_group(*custody_index, &spec) - .expect("should compute custody subnets for node"); - sampling_subnets.extend(subnets); + spec.custody_requirement } - - let mut sampling_columns = HashSet::new(); - for custody_index in &custody_groups { - let columns = compute_columns_for_custody_group(*custody_index, &spec) - .expect("should compute custody columns for node"); - sampling_columns.extend(columns); - } - - (sampling_subnets, sampling_columns) - } else { - (HashSet::new(), HashSet::new()) }; + // The below `expect` calls will panic on start up if the chain spec config values used + // are invalid + let sampling_size = spec + .sampling_size(custody_group_count) + .expect("should compute node sampling size from valid chain spec"); + let custody_groups = get_custody_groups(node_id, sampling_size, &spec) + .expect("should compute node custody groups"); + + let mut sampling_subnets = HashSet::new(); + for custody_index in &custody_groups { + let subnets = compute_subnets_from_custody_group(*custody_index, &spec) + .expect("should compute custody subnets for node"); + sampling_subnets.extend(subnets); + } + + let mut sampling_columns = HashSet::new(); + for custody_index in &custody_groups { + let columns = compute_columns_for_custody_group(*custody_index, &spec) + .expect("should compute custody columns for node"); + sampling_columns.extend(columns); + } + NetworkGlobals { local_enr: RwLock::new(enr.clone()), peer_id: RwLock::new(enr.peer_id()), listen_multiaddrs: RwLock::new(Vec::new()), local_metadata: RwLock::new(local_metadata), - peers: RwLock::new(PeerDB::new(trusted_peers, disable_peer_scoring, log)), + peers: RwLock::new(PeerDB::new(trusted_peers, disable_peer_scoring)), gossipsub_subscriptions: RwLock::new(HashSet::new()), sync_state: RwLock::new(SyncState::Stalled), backfill_state: RwLock::new(BackFillState::Paused), sampling_subnets, sampling_columns, + custody_group_count, config, spec, } @@ -123,6 +120,19 @@ impl NetworkGlobals { self.listen_multiaddrs.read().clone() } + /// Returns true if this node is configured as a PeerDAS supernode + pub fn is_supernode(&self) -> bool { + self.custody_group_count == self.spec.number_of_custody_groups + } + + /// Returns the count of custody columns this node must sample for block import + pub fn custody_columns_count(&self) -> u64 { + // This only panics if the chain spec contains invalid values + self.spec + .sampling_size(self.custody_group_count) + .expect("should compute node sampling size from valid chain spec") + } + /// Returns the number of libp2p connected peers. pub fn connected_peers(&self) -> usize { self.peers.read().connected_peer_ids().count() @@ -162,6 +172,18 @@ impl NetworkGlobals { .unwrap_or_default() } + pub fn add_trusted_peer(&self, enr: Enr) { + self.peers.write().set_trusted_peer(enr); + } + + pub fn remove_trusted_peer(&self, enr: Enr) { + self.peers.write().unset_trusted_peer(enr); + } + + pub fn trusted_peers(&self) -> Vec { + self.peers.read().trusted_peers() + } + /// Updates the syncing state of the node. /// /// The old state is returned @@ -197,7 +219,6 @@ impl NetworkGlobals { /// TESTING ONLY. Build a dummy NetworkGlobals instance. pub fn new_test_globals( trusted_peers: Vec, - log: &slog::Logger, config: Arc, spec: Arc, ) -> NetworkGlobals { @@ -207,13 +228,12 @@ impl NetworkGlobals { syncnets: Default::default(), custody_group_count: spec.custody_requirement, }); - Self::new_test_globals_with_metadata(trusted_peers, metadata, log, config, spec) + Self::new_test_globals_with_metadata(trusted_peers, metadata, config, spec) } pub(crate) fn new_test_globals_with_metadata( trusted_peers: Vec, metadata: MetaData, - log: &slog::Logger, config: Arc, spec: Arc, ) -> NetworkGlobals { @@ -221,18 +241,19 @@ impl NetworkGlobals { let keypair = libp2p::identity::secp256k1::Keypair::generate(); let enr_key: discv5::enr::CombinedKey = discv5::enr::CombinedKey::from_secp256k1(&keypair); let enr = discv5::enr::Enr::builder().build(&enr_key).unwrap(); - NetworkGlobals::new(enr, metadata, trusted_peers, false, log, config, spec) + NetworkGlobals::new(enr, metadata, trusted_peers, false, config, spec) } } #[cfg(test)] mod test { use super::*; + use logging::create_test_tracing_subscriber; use types::{Epoch, EthSpec, MainnetEthSpec as E}; #[test] fn test_sampling_subnets() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let mut spec = E::default_spec(); spec.fulu_fork_epoch = Some(Epoch::new(0)); @@ -244,7 +265,6 @@ mod test { let globals = NetworkGlobals::::new_test_globals_with_metadata( vec![], metadata, - &log, config, Arc::new(spec), ); @@ -256,7 +276,7 @@ mod test { #[test] fn test_sampling_columns() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let mut spec = E::default_spec(); spec.fulu_fork_epoch = Some(Epoch::new(0)); @@ -268,7 +288,6 @@ mod test { let globals = NetworkGlobals::::new_test_globals_with_metadata( vec![], metadata, - &log, config, Arc::new(spec), ); diff --git a/beacon_node/lighthouse_network/src/types/mod.rs b/beacon_node/lighthouse_network/src/types/mod.rs index db92f05b8f..868cdb6eb9 100644 --- a/beacon_node/lighthouse_network/src/types/mod.rs +++ b/beacon_node/lighthouse_network/src/types/mod.rs @@ -1,7 +1,6 @@ mod globals; mod pubsub; mod subnet; -mod sync_state; mod topics; use types::{BitVector, EthSpec}; @@ -11,10 +10,10 @@ pub type EnrSyncCommitteeBitfield = BitVector<::SyncCommitteeSu pub type Enr = discv5::enr::Enr; +pub use eth2::lighthouse::sync_state::{BackFillState, SyncState}; pub use globals::NetworkGlobals; pub use pubsub::{PubsubMessage, SnappyTransform}; pub use subnet::{Subnet, SubnetDiscovery}; -pub use sync_state::{BackFillState, SyncState}; pub use topics::{ all_topics_at_fork, core_topics_to_subscribe, is_fork_non_core_topic, subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, TopicConfig, diff --git a/beacon_node/lighthouse_network/tests/common.rs b/beacon_node/lighthouse_network/tests/common.rs index 6a3ec6dd32..d686885ff7 100644 --- a/beacon_node/lighthouse_network/tests/common.rs +++ b/beacon_node/lighthouse_network/tests/common.rs @@ -4,10 +4,11 @@ use lighthouse_network::Enr; use lighthouse_network::EnrExt; use lighthouse_network::Multiaddr; use lighthouse_network::{NetworkConfig, NetworkEvent}; -use slog::{debug, error, o, Drain}; use std::sync::Arc; use std::sync::Weak; use tokio::runtime::Runtime; +use tracing::{debug, error, info_span, Instrument}; +use tracing_subscriber::EnvFilter; use types::{ ChainSpec, EnrForkId, Epoch, EthSpec, FixedBytesExtended, ForkContext, ForkName, Hash256, MinimalEthSpec, Slot, @@ -67,15 +68,12 @@ impl std::ops::DerefMut for Libp2pInstance { } #[allow(unused)] -pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - +pub fn build_tracing_subscriber(level: &str, enabled: bool) { if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new(level).unwrap()) + .try_init() + .unwrap(); } } @@ -101,16 +99,16 @@ pub fn build_config(mut boot_nodes: Vec) -> Arc { pub async fn build_libp2p_instance( rt: Weak, boot_nodes: Vec, - log: slog::Logger, fork_name: ForkName, chain_spec: Arc, + service_name: String, ) -> Libp2pInstance { let config = build_config(boot_nodes); // launch libp2p service let (signal, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = task_executor::TaskExecutor::new(rt, exit, log.clone(), shutdown_tx); + let executor = task_executor::TaskExecutor::new(rt, exit, shutdown_tx, service_name); let libp2p_context = lighthouse_network::Context { config, enr_fork_id: EnrForkId::default(), @@ -119,7 +117,7 @@ pub async fn build_libp2p_instance( libp2p_registry: None, }; Libp2pInstance( - LibP2PService::new(executor, libp2p_context, &log) + LibP2PService::new(executor, libp2p_context) .await .expect("should build libp2p instance") .0, @@ -143,18 +141,20 @@ pub enum Protocol { #[allow(dead_code)] pub async fn build_node_pair( rt: Weak, - log: &slog::Logger, fork_name: ForkName, spec: Arc, protocol: Protocol, ) -> (Libp2pInstance, Libp2pInstance) { - let sender_log = log.new(o!("who" => "sender")); - let receiver_log = log.new(o!("who" => "receiver")); - - let mut sender = - build_libp2p_instance(rt.clone(), vec![], sender_log, fork_name, spec.clone()).await; + let mut sender = build_libp2p_instance( + rt.clone(), + vec![], + fork_name, + spec.clone(), + "sender".to_string(), + ) + .await; let mut receiver = - build_libp2p_instance(rt, vec![], receiver_log, fork_name, spec.clone()).await; + build_libp2p_instance(rt, vec![], fork_name, spec.clone(), "receiver".to_string()).await; // let the two nodes set up listeners let sender_fut = async { @@ -179,7 +179,8 @@ pub async fn build_node_pair( } } } - }; + } + .instrument(info_span!("Sender", who = "sender")); let receiver_fut = async { loop { if let NetworkEvent::NewListenAddr(addr) = receiver.next_event().await { @@ -201,7 +202,8 @@ pub async fn build_node_pair( } } } - }; + } + .instrument(info_span!("Receiver", who = "receiver")); let joined = futures::future::join(sender_fut, receiver_fut); @@ -209,9 +211,9 @@ pub async fn build_node_pair( match sender.testing_dial(receiver_multiaddr.clone()) { Ok(()) => { - debug!(log, "Sender dialed receiver"; "address" => format!("{:?}", receiver_multiaddr)) + debug!(address = ?receiver_multiaddr, "Sender dialed receiver") } - Err(_) => error!(log, "Dialing failed"), + Err(_) => error!("Dialing failed"), }; (sender, receiver) } @@ -220,7 +222,6 @@ pub async fn build_node_pair( #[allow(dead_code)] pub async fn build_linear( rt: Weak, - log: slog::Logger, n: usize, fork_name: ForkName, spec: Arc, @@ -228,7 +229,14 @@ pub async fn build_linear( let mut nodes = Vec::with_capacity(n); for _ in 0..n { nodes.push( - build_libp2p_instance(rt.clone(), vec![], log.clone(), fork_name, spec.clone()).await, + build_libp2p_instance( + rt.clone(), + vec![], + fork_name, + spec.clone(), + "linear".to_string(), + ) + .await, ); } @@ -238,8 +246,8 @@ pub async fn build_linear( .collect(); for i in 0..n - 1 { match nodes[i].testing_dial(multiaddrs[i + 1].clone()) { - Ok(()) => debug!(log, "Connected"), - Err(_) => error!(log, "Failed to connect"), + Ok(()) => debug!("Connected"), + Err(_) => error!("Failed to connect"), }; } nodes diff --git a/beacon_node/lighthouse_network/tests/rpc_tests.rs b/beacon_node/lighthouse_network/tests/rpc_tests.rs index 4b54a24ddc..d736fefa5f 100644 --- a/beacon_node/lighthouse_network/tests/rpc_tests.rs +++ b/beacon_node/lighthouse_network/tests/rpc_tests.rs @@ -2,17 +2,17 @@ mod common; -use common::Protocol; +use common::{build_tracing_subscriber, Protocol}; use lighthouse_network::rpc::{methods::*, RequestType}; use lighthouse_network::service::api_types::AppRequestId; use lighthouse_network::{rpc::max_rpc_size, NetworkEvent, ReportSource, Response}; -use slog::{debug, warn, Level}; use ssz::Encode; use ssz_types::VariableList; use std::sync::Arc; use std::time::Duration; use tokio::runtime::Runtime; use tokio::time::sleep; +use tracing::{debug, warn}; use types::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BlobSidecar, ChainSpec, EmptyBlock, Epoch, EthSpec, FixedBytesExtended, ForkContext, ForkName, Hash256, MinimalEthSpec, @@ -53,26 +53,19 @@ fn bellatrix_block_large(fork_context: &ForkContext, spec: &ChainSpec) -> Beacon #[test] #[allow(clippy::single_match)] fn test_tcp_status_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); - let log = common::build_log(log_level, enable_logging); - let spec = Arc::new(E::default_spec()); rt.block_on(async { // get sender/receiver - let (mut sender, mut receiver) = common::build_node_pair( - Arc::downgrade(&rt), - &log, - ForkName::Base, - spec, - Protocol::Tcp, - ) - .await; + let (mut sender, mut receiver) = + common::build_node_pair(Arc::downgrade(&rt), ForkName::Base, spec, Protocol::Tcp).await; // Dummy STATUS RPC message let rpc_request = RequestType::Status(StatusMessage { @@ -98,7 +91,7 @@ fn test_tcp_status_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -109,9 +102,9 @@ fn test_tcp_status_rpc() { response, } => { // Should receive the RPC response - debug!(log, "Sender Received"); + debug!("Sender Received"); assert_eq!(response, rpc_response.clone()); - debug!(log, "Sender Completed"); + debug!("Sender Completed"); return; } _ => {} @@ -130,7 +123,7 @@ fn test_tcp_status_rpc() { } => { if request.r#type == rpc_request { // send the response - debug!(log, "Receiver Received"); + debug!("Receiver Received"); receiver.send_response(peer_id, id, request.id, rpc_response.clone()); } } @@ -153,14 +146,13 @@ fn test_tcp_status_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 6; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -169,7 +161,6 @@ fn test_tcp_blocks_by_range_chunked_rpc() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -206,7 +197,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -216,7 +207,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { id: _, response, } => { - warn!(log, "Sender received a response"); + warn!("Sender received a response"); match response { Response::BlocksByRange(Some(_)) => { if messages_received < 2 { @@ -227,7 +218,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { assert_eq!(response, rpc_response_bellatrix_small.clone()); } messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlocksByRange(None) => { // should be exactly `messages_to_send` messages before terminating @@ -254,7 +245,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for i in 0..messages_to_send { // Send first third of responses as base blocks, // second as altair and third as bellatrix. @@ -300,15 +291,14 @@ fn test_tcp_blocks_by_range_chunked_rpc() { #[test] #[allow(clippy::single_match)] fn test_blobs_by_range_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let slot_count = 32; let messages_to_send = 34; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); rt.block_on(async { @@ -316,7 +306,6 @@ fn test_blobs_by_range_chunked_rpc() { let spec = Arc::new(E::default_spec()); let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Deneb, spec.clone(), Protocol::Tcp, @@ -342,7 +331,7 @@ fn test_blobs_by_range_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -352,12 +341,12 @@ fn test_blobs_by_range_chunked_rpc() { id: _, response, } => { - warn!(log, "Sender received a response"); + warn!("Sender received a response"); match response { Response::BlobsByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlobsByRange(None) => { // should be exactly `messages_to_send` messages before terminating @@ -384,7 +373,7 @@ fn test_blobs_by_range_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 0..messages_to_send { // Send first third of responses as base blocks, // second as altair and third as bellatrix. @@ -423,14 +412,13 @@ fn test_blobs_by_range_chunked_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_over_limit() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 5; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -439,7 +427,6 @@ fn test_tcp_blocks_by_range_over_limit() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -466,7 +453,7 @@ fn test_tcp_blocks_by_range_over_limit() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -492,7 +479,7 @@ fn test_tcp_blocks_by_range_over_limit() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 0..messages_to_send { let rpc_response = rpc_response_bellatrix_large.clone(); receiver.send_response( @@ -529,15 +516,14 @@ fn test_tcp_blocks_by_range_over_limit() { // Tests that a streamed BlocksByRange RPC Message terminates when all expected chunks were received #[test] fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 10; let extra_messages_to_send = 10; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -546,7 +532,6 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -574,7 +559,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -586,7 +571,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { } => // Should receive the RPC response { - debug!(log, "Sender received a response"); + debug!("Sender received a response"); match response { Response::BlocksByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); @@ -630,7 +615,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { )) => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); message_info = Some((peer_id, id, request.id)); } } @@ -643,7 +628,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { messages_sent += 1; let (peer_id, stream_id, request_id) = message_info.as_ref().unwrap(); receiver.send_response(*peer_id, *stream_id, *request_id, rpc_response.clone()); - debug!(log, "Sending message {}", messages_sent); + debug!("Sending message {}", messages_sent); if messages_sent == messages_to_send + extra_messages_to_send { // stop sending messages return; @@ -666,11 +651,11 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_single_empty_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Trace; + // Set up the logging. + let log_level = "trace"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); - let log = common::build_log(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -679,7 +664,6 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -709,7 +693,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -722,7 +706,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { Response::BlocksByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlocksByRange(None) => { // should be exactly 10 messages before terminating @@ -748,7 +732,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 1..=messages_to_send { receiver.send_response( @@ -788,13 +772,13 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_root_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 6; - let log = common::build_log(log_level, enable_logging); let spec = Arc::new(E::default_spec()); let rt = Arc::new(Runtime::new().unwrap()); @@ -802,7 +786,6 @@ fn test_tcp_blocks_by_root_chunked_rpc() { rt.block_on(async { let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -847,7 +830,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -866,7 +849,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { assert_eq!(response, rpc_response_bellatrix_small.clone()); } messages_received += 1; - debug!(log, "Chunk received"); + debug!("Chunk received"); } Response::BlocksByRoot(None) => { // should be exactly messages_to_send @@ -892,7 +875,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - debug!(log, "Receiver got request"); + debug!("Receiver got request"); for i in 0..messages_to_send { // Send equal base, altair and bellatrix blocks @@ -904,7 +887,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { rpc_response_bellatrix_small.clone() }; receiver.send_response(peer_id, id, request.id, rpc_response); - debug!(log, "Sending message"); + debug!("Sending message"); } // send the stream termination receiver.send_response( @@ -913,7 +896,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { request.id, Response::BlocksByRange(None), ); - debug!(log, "Send stream term"); + debug!("Send stream term"); } } _ => {} // Ignore other events @@ -933,14 +916,14 @@ fn test_tcp_blocks_by_root_chunked_rpc() { // Tests a streamed, chunked BlocksByRoot RPC Message terminates when all expected reponses have been received #[test] fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send: u64 = 10; let extra_messages_to_send: u64 = 10; - let log = common::build_log(log_level, enable_logging); let spec = Arc::new(E::default_spec()); let rt = Arc::new(Runtime::new().unwrap()); @@ -948,7 +931,6 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { rt.block_on(async { let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -988,7 +970,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -998,12 +980,12 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { id: AppRequestId::Router, response, } => { - debug!(log, "Sender received a response"); + debug!("Sender received a response"); match response { Response::BlocksByRoot(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - debug!(log, "Chunk received"); + debug!("Chunk received"); } Response::BlocksByRoot(None) => { // should be exactly messages_to_send @@ -1044,7 +1026,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { )) => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); message_info = Some((peer_id, id, request.id)); } } @@ -1057,7 +1039,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { messages_sent += 1; let (peer_id, stream_id, request_id) = message_info.as_ref().unwrap(); receiver.send_response(*peer_id, *stream_id, *request_id, rpc_response.clone()); - debug!(log, "Sending message {}", messages_sent); + debug!("Sending message {}", messages_sent); if messages_sent == messages_to_send + extra_messages_to_send { // stop sending messages return; @@ -1078,8 +1060,9 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { /// Establishes a pair of nodes and disconnects the pair based on the selected protocol via an RPC /// Goodbye message. -fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { - let log = common::build_log(log_level, enable_logging); +fn goodbye_test(log_level: &str, enable_logging: bool, protocol: Protocol) { + // Set up the logging. + build_tracing_subscriber(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); @@ -1088,8 +1071,7 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { // get sender/receiver rt.block_on(async { let (mut sender, mut receiver) = - common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, spec, protocol) - .await; + common::build_node_pair(Arc::downgrade(&rt), ForkName::Base, spec, protocol).await; // build the sender future let sender_future = async { @@ -1097,7 +1079,7 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a goodbye and disconnect - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender.goodbye_peer( &peer_id, GoodbyeReason::IrrelevantNetwork, @@ -1137,18 +1119,16 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { #[test] #[allow(clippy::single_match)] fn tcp_test_goodbye_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; - let enable_logging = false; - goodbye_test(log_level, enable_logging, Protocol::Tcp); + let log_level = "debug"; + let enabled_logging = false; + goodbye_test(log_level, enabled_logging, Protocol::Tcp); } // Tests a Goodbye RPC message #[test] #[allow(clippy::single_match)] fn quic_test_goodbye_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; - let enable_logging = false; - goodbye_test(log_level, enable_logging, Protocol::Quic); + let log_level = "debug"; + let enabled_logging = false; + goodbye_test(log_level, enabled_logging, Protocol::Quic); } diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 5071e247a3..4e36953880 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -15,9 +15,6 @@ kzg = { workspace = true } matches = "0.1.8" rand_chacha = "0.3.1" serde_json = { workspace = true } -slog-async = { workspace = true } -slog-term = { workspace = true } -sloggers = { workspace = true } [dependencies] alloy-primitives = { workspace = true } @@ -42,7 +39,6 @@ metrics = { workspace = true } operation_pool = { workspace = true } parking_lot = { workspace = true } rand = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } smallvec = { workspace = true } ssz_types = { workspace = true } @@ -51,6 +47,8 @@ strum = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } [features] diff --git a/beacon_node/network/src/nat.rs b/beacon_node/network/src/nat.rs index e63ff55039..ce9d241d43 100644 --- a/beacon_node/network/src/nat.rs +++ b/beacon_node/network/src/nat.rs @@ -5,10 +5,10 @@ use anyhow::{bail, Context, Error}; use igd_next::{aio::tokio as igd, PortMappingProtocol}; -use slog::debug; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; use tokio::time::sleep; +use tracing::debug; /// The duration in seconds of a port mapping on the gateway. const MAPPING_DURATION: u32 = 3600; @@ -17,11 +17,7 @@ const MAPPING_DURATION: u32 = 3600; const MAPPING_TIMEOUT: u64 = MAPPING_DURATION as u64 / 2; /// Attempts to map Discovery external port mappings with UPnP. -pub async fn construct_upnp_mappings( - addr: Ipv4Addr, - port: u16, - log: slog::Logger, -) -> Result<(), Error> { +pub async fn construct_upnp_mappings(addr: Ipv4Addr, port: u16) -> Result<(), Error> { let gateway = igd::search_gateway(Default::default()) .await .context("Gateway does not support UPnP")?; @@ -54,7 +50,7 @@ pub async fn construct_upnp_mappings( ) .await .with_context(|| format!("Could not UPnP map port: {} on the gateway", port))?; - debug!(log, "Discovery UPnP port mapped"; "port" => %port); + debug!(%port,"Discovery UPnP port mapped"); sleep(Duration::from_secs(MAPPING_TIMEOUT)).await; } } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 4338bfbc89..f104bbf1bc 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -21,8 +21,8 @@ use beacon_chain::{ GossipVerifiedBlock, NotifyExecutionLayer, }; use lighthouse_network::{Client, MessageAcceptance, MessageId, PeerAction, PeerId, ReportSource}; +use logging::crit; use operation_pool::ReceivedPreCapella; -use slog::{crit, debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; use std::fs; @@ -32,6 +32,7 @@ use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use store::hot_cold_store::HotColdDBError; use tokio::sync::mpsc; +use tracing::{debug, error, info, trace, warn}; use types::{ beacon_block::BlockImportSource, Attestation, AttestationData, AttestationRef, AttesterSlashing, BlobSidecar, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, @@ -251,9 +252,8 @@ impl NetworkBeaconProcessor { Ok(results) => results, Err(e) => { error!( - self.log, - "Batch unagg. attn verification failed"; - "error" => ?e + error = ?e, + "Batch unagg. attn verification failed" ); return; } @@ -264,10 +264,9 @@ impl NetworkBeaconProcessor { // The log is `crit` since in this scenario we might be penalizing/rewarding the wrong // peer. crit!( - self.log, - "Batch attestation result mismatch"; - "results" => results.len(), - "packages" => packages.len(), + results = results.len(), + packages = packages.len(), + "Batch attestation result mismatch" ) } @@ -355,19 +354,17 @@ impl NetworkBeaconProcessor { e, )) => { debug!( - self.log, - "Attestation invalid for fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for fork choice" ) } e => error!( - self.log, - "Error applying attestation to fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Error applying attestation to fork choice" ), } } @@ -377,11 +374,10 @@ impl NetworkBeaconProcessor { .add_to_naive_aggregation_pool(&verified_attestation) { debug!( - self.log, - "Attestation invalid for agg pool"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for agg pool" ) } @@ -459,10 +455,9 @@ impl NetworkBeaconProcessor { seen_timestamp, ) { error!( - &self.log, - "Unable to queue converted SingleAttestation"; - "error" => %e, - "slot" => slot, + error = %e, + %slot, + "Unable to queue converted SingleAttestation" ); self.propagate_validation_result( message_id, @@ -486,11 +481,7 @@ impl NetworkBeaconProcessor { beacon_block_root, )) .unwrap_or_else(|_| { - warn!( - self.log, - "Failed to send to sync service"; - "msg" => "UnknownBlockHash" - ) + warn!(msg = "UnknownBlockHash", "Failed to send to sync service") }); let processor = self.clone(); // Do not allow this attestation to be re-processed beyond this point. @@ -510,10 +501,7 @@ impl NetworkBeaconProcessor { }), }); if sender.try_send(reprocess_msg).is_err() { - error!( - self.log, - "Failed to send attestation for re-processing"; - ) + error!("Failed to send attestation for re-processing") } } else { // We shouldn't make any further attempts to process this attestation. @@ -611,9 +599,8 @@ impl NetworkBeaconProcessor { Ok(results) => results, Err(e) => { error!( - self.log, - "Batch agg. attn verification failed"; - "error" => ?e + error = ?e, + "Batch agg. attn verification failed" ); return; } @@ -624,10 +611,9 @@ impl NetworkBeaconProcessor { // The log is `crit` since in this scenario we might be penalizing/rewarding the wrong // peer. crit!( - self.log, - "Batch agg. attestation result mismatch"; - "results" => results.len(), - "packages" => packages.len(), + results = results.len(), + packages = packages.len(), + "Batch agg. attestation result mismatch" ) } @@ -707,30 +693,27 @@ impl NetworkBeaconProcessor { e, )) => { debug!( - self.log, - "Aggregate invalid for fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Aggregate invalid for fork choice" ) } e => error!( - self.log, - "Error applying aggregate to fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Error applying aggregate to fork choice" ), } } if let Err(e) = self.chain.add_to_block_inclusion_pool(verified_aggregate) { debug!( - self.log, - "Attestation invalid for op pool"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for op pool" ) } @@ -786,11 +769,10 @@ impl NetworkBeaconProcessor { ); debug!( - self.log, - "Successfully verified gossip data column sidecar"; - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + %slot, + %block_root, + %index, + "Successfully verified gossip data column sidecar" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -817,11 +799,10 @@ impl NetworkBeaconProcessor { match err { GossipDataColumnError::ParentUnknown { parent_root } => { debug!( - self.log, - "Unknown parent hash for column"; - "action" => "requesting parent", - "block_root" => %block_root, - "parent_root" => %parent_root, + action = "requesting parent", + %block_root, + %parent_root, + "Unknown parent hash for column" ); self.send_sync_message(SyncMessage::UnknownParentDataColumn( peer_id, @@ -831,9 +812,8 @@ impl NetworkBeaconProcessor { GossipDataColumnError::PubkeyCacheTimeout | GossipDataColumnError::BeaconChainError(_) => { crit!( - self.log, - "Internal error when verifying column sidecar"; - "error" => ?err, + error = ?err, + "Internal error when verifying column sidecar" ) } GossipDataColumnError::ProposalSignatureInvalid @@ -848,12 +828,11 @@ impl NetworkBeaconProcessor { | GossipDataColumnError::InconsistentCommitmentsOrProofLength | GossipDataColumnError::NotFinalizedDescendant { .. } => { debug!( - self.log, - "Could not verify column sidecar for gossip. Rejecting the column sidecar"; - "error" => ?err, - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + error = ?err, + %slot, + %block_root, + %index, + "Could not verify column sidecar for gossip. Rejecting the column sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -872,22 +851,20 @@ impl NetworkBeaconProcessor { // Do not penalise the peer. // Gossip filter should filter any duplicates received after this. debug!( - self.log, - "Received already available column sidecar. Ignoring the column sidecar"; - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + %slot, + %block_root, + %index, + "Received already available column sidecar. Ignoring the column sidecar" ) } GossipDataColumnError::FutureSlot { .. } | GossipDataColumnError::PastFinalizedSlot { .. } => { debug!( - self.log, - "Could not verify column sidecar for gossip. Ignoring the column sidecar"; - "error" => ?err, - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + error = ?err, + %slot, + %block_root, + %index, + "Could not verify column sidecar for gossip. Ignoring the column sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -933,23 +910,21 @@ impl NetworkBeaconProcessor { if delay >= self.chain.slot_clock.unagg_attestation_production_delay() { metrics::inc_counter(&metrics::BEACON_BLOB_GOSSIP_ARRIVED_LATE_TOTAL); debug!( - self.log, - "Gossip blob arrived late"; - "block_root" => ?gossip_verified_blob.block_root(), - "proposer_index" => gossip_verified_blob.block_proposer_index(), - "slot" => gossip_verified_blob.slot(), - "delay" => ?delay, - "commitment" => %gossip_verified_blob.kzg_commitment(), + block_root = ?gossip_verified_blob.block_root(), + proposer_index = gossip_verified_blob.block_proposer_index(), + slot = %gossip_verified_blob.slot(), + delay = ?delay, + commitment = %gossip_verified_blob.kzg_commitment(), + "Gossip blob arrived late" ); } debug!( - self.log, - "Successfully verified gossip blob"; - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %gossip_verified_blob.kzg_commitment(), + %slot, + %root, + %index, + commitment = %gossip_verified_blob.kzg_commitment(), + "Successfully verified gossip blob" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -972,12 +947,11 @@ impl NetworkBeaconProcessor { match err { GossipBlobError::BlobParentUnknown { parent_root } => { debug!( - self.log, - "Unknown parent hash for blob"; - "action" => "requesting parent", - "block_root" => %root, - "parent_root" => %parent_root, - "commitment" => %commitment, + action = "requesting parent", + block_root = %root, + parent_root = %parent_root, + %commitment, + "Unknown parent hash for blob" ); self.send_sync_message(SyncMessage::UnknownParentBlob( peer_id, @@ -986,9 +960,8 @@ impl NetworkBeaconProcessor { } GossipBlobError::PubkeyCacheTimeout | GossipBlobError::BeaconChainError(_) => { crit!( - self.log, - "Internal error when verifying blob sidecar"; - "error" => ?err, + error = ?err, + "Internal error when verifying blob sidecar" ) } GossipBlobError::ProposalSignatureInvalid @@ -1000,13 +973,12 @@ impl NetworkBeaconProcessor { | GossipBlobError::KzgError(_) | GossipBlobError::NotFinalizedDescendant { .. } => { warn!( - self.log, - "Could not verify blob sidecar for gossip. Rejecting the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Rejecting the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer. self.gossip_penalize_peer( @@ -1024,22 +996,20 @@ impl NetworkBeaconProcessor { // We may have received the blob from the EL. Do not penalise the peer. // Gossip filter should filter any duplicates received after this. debug!( - self.log, - "Received already available blob sidecar. Ignoring the blob sidecar"; - "slot" => %slot, - "root" => %root, - "index" => %index, + %slot, + %root, + %index, + "Received already available blob sidecar. Ignoring the blob sidecar" ) } GossipBlobError::FutureSlot { .. } => { debug!( - self.log, - "Could not verify blob sidecar for gossip. Ignoring the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Ignoring the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -1055,13 +1025,12 @@ impl NetworkBeaconProcessor { } GossipBlobError::PastFinalizedSlot { .. } => { debug!( - self.log, - "Could not verify blob sidecar for gossip. Ignoring the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Ignoring the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer. A low-tolerance // error is fine because there's no reason for peers to be propagating old @@ -1099,9 +1068,8 @@ impl NetworkBeaconProcessor { match &result { Ok(AvailabilityProcessingStatus::Imported(block_root)) => { info!( - self.log, - "Gossipsub blob processed - imported fully available block"; - "block_root" => %block_root + %block_root, + "Gossipsub blob processed - imported fully available block" ); self.chain.recompute_head_at_current_slot().await; @@ -1112,29 +1080,25 @@ impl NetworkBeaconProcessor { } Ok(AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { debug!( - self.log, - "Processed gossip blob - waiting for other components"; - "slot" => %slot, - "blob_index" => %blob_index, - "block_root" => %block_root, + %slot, + %blob_index, + %block_root, + "Processed gossip blob - waiting for other components" ); } Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Ignoring gossip blob already imported"; - "block_root" => ?block_root, - "blob_index" => blob_index, + ?block_root, + blob_index, "Ignoring gossip blob already imported" ); } Err(err) => { debug!( - self.log, - "Invalid gossip blob"; - "outcome" => ?err, - "block_root" => ?block_root, - "block_slot" => blob_slot, - "blob_index" => blob_index, + outcome = ?err, + ?block_root, + %blob_slot, + blob_index, + "Invalid gossip blob" ); self.gossip_penalize_peer( peer_id, @@ -1177,9 +1141,8 @@ impl NetworkBeaconProcessor { Ok(availability) => match availability { AvailabilityProcessingStatus::Imported(block_root) => { info!( - self.log, - "Gossipsub data column processed, imported fully available block"; - "block_root" => %block_root + %block_root, + "Gossipsub data column processed, imported fully available block" ); self.chain.recompute_head_at_current_slot().await; @@ -1190,11 +1153,10 @@ impl NetworkBeaconProcessor { } AvailabilityProcessingStatus::MissingComponents(slot, block_root) => { trace!( - self.log, - "Processed data column, waiting for other components"; - "slot" => %slot, - "data_column_index" => %data_column_index, - "block_root" => %block_root, + %slot, + %data_column_index, + %block_root, + "Processed data column, waiting for other components" ); self.attempt_data_column_reconstruction(block_root).await; @@ -1202,20 +1164,17 @@ impl NetworkBeaconProcessor { }, Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Ignoring gossip column already imported"; - "block_root" => ?block_root, - "data_column_index" => data_column_index, + ?block_root, + data_column_index, "Ignoring gossip column already imported" ); } Err(err) => { debug!( - self.log, - "Invalid gossip data column"; - "outcome" => ?err, - "block root" => ?block_root, - "block slot" => data_column_slot, - "data column index" => data_column_index, + outcome = ?err, + ?block_root, + block_slot = %data_column_slot, + data_column_index, + "Invalid gossip data column" ); self.gossip_penalize_peer( peer_id, @@ -1271,9 +1230,8 @@ impl NetworkBeaconProcessor { drop(handle); } else { debug!( - self.log, - "RPC block is being imported"; - "block_root" => %block_root, + %block_root, + "RPC block is being imported" ); } } @@ -1299,7 +1257,10 @@ impl NetworkBeaconProcessor { let verification_result = self .chain .clone() - .verify_block_for_gossip(block.clone()) + .verify_block_for_gossip( + block.clone(), + self.network_globals.custody_columns_count() as usize, + ) .await; if verification_result.is_ok() { @@ -1329,20 +1290,18 @@ impl NetworkBeaconProcessor { if block_delay >= self.chain.slot_clock.unagg_attestation_production_delay() { metrics::inc_counter(&metrics::BEACON_BLOCK_DELAY_GOSSIP_ARRIVED_LATE_TOTAL); debug!( - self.log, - "Gossip block arrived late"; - "block_root" => ?verified_block.block_root, - "proposer_index" => verified_block.block.message().proposer_index(), - "slot" => verified_block.block.slot(), - "block_delay" => ?block_delay, + block_root = ?verified_block.block_root, + proposer_index = verified_block.block.message().proposer_index(), + slot = ?verified_block.block.slot(), + ?block_delay, + "Gossip block arrived late" ); } info!( - self.log, - "New block received"; - "slot" => verified_block.block.slot(), - "root" => ?verified_block.block_root + slot = %verified_block.block.slot(), + root = ?verified_block.block_root, + "New block received" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -1362,9 +1321,8 @@ impl NetworkBeaconProcessor { } Err(e @ BlockError::Slashable) => { warn!( - self.log, - "Received equivocating block from peer"; - "error" => ?e + error = ?e, + "Received equivocating block from peer" ); /* punish peer for submitting an equivocation, but not too harshly as honest peers may conceivably forward equivocating blocks to us from time to time */ self.gossip_penalize_peer( @@ -1375,19 +1333,14 @@ impl NetworkBeaconProcessor { return None; } Err(BlockError::ParentUnknown { .. }) => { - debug!( - self.log, - "Unknown parent for gossip block"; - "root" => ?block_root - ); + debug!(?block_root, "Unknown parent for gossip block"); self.send_sync_message(SyncMessage::UnknownParentBlock(peer_id, block, block_root)); return None; } Err(e @ BlockError::BeaconChainError(_)) => { debug!( - self.log, - "Gossip block beacon chain error"; - "error" => ?e, + error = ?e, + "Gossip block beacon chain error" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; @@ -1397,18 +1350,16 @@ impl NetworkBeaconProcessor { | BlockError::DuplicateImportStatusUnknown(..), ) => { debug!( - self.log, - "Gossip block is already known"; - "block_root" => %block_root, + %block_root, + "Gossip block is already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; } Err(e @ BlockError::FutureSlot { .. }) => { debug!( - self.log, - "Could not verify block for gossip. Ignoring the block"; - "error" => %e + error = %e, + "Could not verify block for gossip. Ignoring the block" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -1422,9 +1373,8 @@ impl NetworkBeaconProcessor { Err(e @ BlockError::WouldRevertFinalizedSlot { .. }) | Err(e @ BlockError::NotFinalizedDescendant { .. }) => { debug!( - self.log, - "Could not verify block for gossip. Ignoring the block"; - "error" => %e + error = %e, + "Could not verify block for gossip. Ignoring the block" ); // The spec says we must IGNORE these blocks but there's no reason for an honest // and non-buggy client to be gossiping blocks that blatantly conflict with @@ -1439,8 +1389,7 @@ impl NetworkBeaconProcessor { return None; } Err(ref e @ BlockError::ExecutionPayloadError(ref epe)) if !epe.penalize_peer() => { - debug!(self.log, "Could not verify block for gossip. Ignoring the block"; - "error" => %e); + debug!(error = %e, "Could not verify block for gossip. Ignoring the block"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; } @@ -1457,9 +1406,9 @@ impl NetworkBeaconProcessor { | Err(e @ BlockError::InconsistentFork(_)) | Err(e @ BlockError::ExecutionPayloadError(_)) | Err(e @ BlockError::ParentExecutionPayloadInvalid { .. }) + | Err(e @ BlockError::KnownInvalidExecutionPayload(_)) | Err(e @ BlockError::GenesisBlock) => { - warn!(self.log, "Could not verify block for gossip. Rejecting the block"; - "error" => %e); + warn!(error = %e, "Could not verify block for gossip. Rejecting the block"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( peer_id, @@ -1471,17 +1420,12 @@ impl NetworkBeaconProcessor { // Note: This error variant cannot be reached when doing gossip validation // as we do not do availability checks here. Err(e @ BlockError::AvailabilityCheck(_)) => { - crit!(self.log, "Internal block gossip validation error. Availability check during - gossip validation"; - "error" => %e - ); + crit!(error = %e, "Internal block gossip validation error. Availability check during gossip validation"); return None; } // BlobNotRequired is unreachable. Only constructed in `process_gossip_blob` Err(e @ BlockError::InternalError(_)) | Err(e @ BlockError::BlobNotRequired(_)) => { - error!(self.log, "Internal block gossip validation error"; - "error" => %e - ); + error!(error = %e, "Internal block gossip validation error"); return None; } }; @@ -1510,11 +1454,10 @@ impl NetworkBeaconProcessor { // tolerance for block imports. Ok(current_slot) if block_slot > current_slot => { warn!( - self.log, - "Block arrived early"; - "block_slot" => %block_slot, - "block_root" => ?block_root, - "msg" => "if this happens consistently, check system clock" + %block_slot, + ?block_root, + msg = "if this happens consistently, check system clock", + "Block arrived early" ); // Take note of how early this block arrived. @@ -1555,11 +1498,10 @@ impl NetworkBeaconProcessor { .is_err() { error!( - self.log, - "Failed to defer block import"; - "block_slot" => %block_slot, - "block_root" => ?block_root, - "location" => "block gossip" + %block_slot, + ?block_root, + location = "block gossip", + "Failed to defer block import" ) } None @@ -1567,12 +1509,11 @@ impl NetworkBeaconProcessor { Ok(_) => Some(verified_block), Err(e) => { error!( - self.log, - "Failed to defer block import"; - "error" => ?e, - "block_slot" => %block_slot, - "block_root" => ?block_root, - "location" => "block gossip" + error = ?e, + %block_slot, + ?block_root, + location = "block gossip", + "Failed to defer block import" ); None } @@ -1644,18 +1585,16 @@ impl NetworkBeaconProcessor { .is_err() { error!( - self.log, - "Failed to inform block import"; - "source" => "gossip", - "block_root" => ?block_root, + source = "gossip", + ?block_root, + "Failed to inform block import" ) }; debug!( - self.log, - "Gossipsub block processed"; - "block" => ?block_root, - "peer_id" => %peer_id + ?block_root, + %peer_id, + "Gossipsub block processed" ); self.chain.recompute_head_at_current_slot().await; @@ -1667,10 +1606,9 @@ impl NetworkBeaconProcessor { } Ok(AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { trace!( - self.log, - "Processed block, waiting for other components"; - "slot" => slot, - "block_root" => %block_root, + %slot, + %block_root, + "Processed block, waiting for other components" ); } Err(BlockError::ParentUnknown { .. }) => { @@ -1680,26 +1618,23 @@ impl NetworkBeaconProcessor { // can recover by receiving another block / blob / attestation referencing the // chain that includes this block. error!( - self.log, - "Block with unknown parent attempted to be processed"; - "block_root" => %block_root, - "peer_id" => %peer_id + %block_root, + %peer_id, + "Block with unknown parent attempted to be processed" ); } Err(ref e @ BlockError::ExecutionPayloadError(ref epe)) if !epe.penalize_peer() => { debug!( - self.log, - "Failed to verify execution payload"; - "error" => %e + error = %e, + "Failed to verify execution payload" ); } Err(BlockError::AvailabilityCheck(err)) => { match err.category() { AvailabilityCheckErrorCategory::Internal => { warn!( - self.log, - "Internal availability check error"; - "error" => ?err, + error = ?err, + "Internal availability check error" ); } AvailabilityCheckErrorCategory::Malicious => { @@ -1711,20 +1646,18 @@ impl NetworkBeaconProcessor { // 2. The proposer being malicious and sending inconsistent // blocks and blobs. warn!( - self.log, - "Received invalid blob or malicious proposer"; - "error" => ?err + error = ?err, + "Received invalid blob or malicious proposer" ); } } } other => { debug!( - self.log, - "Invalid gossip beacon block"; - "outcome" => ?other, - "block root" => ?block_root, - "block slot" => block.slot() + outcome = ?other, + ?block_root, + block_slot = %block.slot(), + "Invalid gossip beacon block" ); self.gossip_penalize_peer( peer_id, @@ -1732,21 +1665,14 @@ impl NetworkBeaconProcessor { "bad_gossip_block_ssz", ); trace!( - self.log, - "Invalid gossip beacon block ssz"; - "ssz" => format_args!("0x{}", hex::encode(block.as_ssz_bytes())), + ssz = format_args!("0x{}", hex::encode(block.as_ssz_bytes())), + "Invalid gossip beacon block ssz" ); } }; if let Err(e) = &result { - self.maybe_store_invalid_block( - &invalid_block_storage, - block_root, - &block, - e, - &self.log, - ); + self.maybe_store_invalid_block(&invalid_block_storage, block_root, &block, e); } self.send_sync_message(SyncMessage::GossipBlockProcessResult { @@ -1768,20 +1694,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::AlreadyKnown) => { self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); debug!( - self.log, - "Dropping exit for already exiting validator"; - "validator_index" => validator_index, - "peer" => %peer_id + validator_index, + peer = %peer_id, + "Dropping exit for already exiting validator" ); return; } Err(e) => { debug!( - self.log, - "Dropping invalid exit"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid exit" ); // These errors occur due to a fault in the beacon chain. It is not necessarily // the fault on the peer. @@ -1808,7 +1732,7 @@ impl NetworkBeaconProcessor { self.chain.import_voluntary_exit(exit); - debug!(self.log, "Successfully imported voluntary exit"); + debug!("Successfully imported voluntary exit"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_EXIT_IMPORTED_TOTAL); } @@ -1828,11 +1752,10 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::New(slashing)) => slashing, Ok(ObservationOutcome::AlreadyKnown) => { debug!( - self.log, - "Dropping proposer slashing"; - "reason" => "Already seen a proposer slashing for that validator", - "validator_index" => validator_index, - "peer" => %peer_id + reason = "Already seen a proposer slashing for that validator", + validator_index, + peer = %peer_id, + "Dropping proposer slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -1841,11 +1764,10 @@ impl NetworkBeaconProcessor { // This is likely a fault with the beacon chain and not necessarily a // malicious message from the peer. debug!( - self.log, - "Dropping invalid proposer slashing"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid proposer slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -1870,7 +1792,7 @@ impl NetworkBeaconProcessor { .register_gossip_proposer_slashing(slashing.as_inner()); self.chain.import_proposer_slashing(slashing); - debug!(self.log, "Successfully imported proposer slashing"); + debug!("Successfully imported proposer slashing"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_PROPOSER_SLASHING_IMPORTED_TOTAL); } @@ -1888,20 +1810,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::New(slashing)) => slashing, Ok(ObservationOutcome::AlreadyKnown) => { debug!( - self.log, - "Dropping attester slashing"; - "reason" => "Slashings already known for all slashed validators", - "peer" => %peer_id + reason = "Slashings already known for all slashed validators", + peer = %peer_id, + "Dropping attester slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; } Err(e) => { debug!( - self.log, - "Dropping invalid attester slashing"; - "peer" => %peer_id, - "error" => ?e + %peer_id, + error = ?e, + "Dropping invalid attester slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize peer slightly for invalids. @@ -1925,7 +1845,7 @@ impl NetworkBeaconProcessor { .register_gossip_attester_slashing(slashing.as_inner().to_ref()); self.chain.import_attester_slashing(slashing); - debug!(self.log, "Successfully imported attester slashing"); + debug!("Successfully imported attester slashing"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_ATTESTER_SLASHING_IMPORTED_TOTAL); } @@ -1946,20 +1866,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::AlreadyKnown) => { self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); debug!( - self.log, - "Dropping BLS to execution change"; - "validator_index" => validator_index, - "peer" => %peer_id + validator_index, + peer = %peer_id, + "Dropping BLS to execution change" ); return; } Err(e) => { debug!( - self.log, - "Dropping invalid BLS to execution change"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid BLS to execution change" ); // We ignore pre-capella messages without penalizing peers. if matches!(e, BeaconChainError::BlsToExecutionPriorToCapella) { @@ -1997,10 +1915,9 @@ impl NetworkBeaconProcessor { .import_bls_to_execution_change(change, received_pre_capella); debug!( - self.log, - "Successfully imported BLS to execution change"; - "validator_index" => validator_index, - "address" => ?address, + validator_index, + ?address, + "Successfully imported BLS to execution change" ); metrics::inc_counter(&metrics::BEACON_PROCESSOR_BLS_TO_EXECUTION_CHANGE_IMPORTED_TOTAL); @@ -2059,10 +1976,9 @@ impl NetworkBeaconProcessor { .add_to_naive_sync_aggregation_pool(sync_signature) { debug!( - self.log, - "Sync committee signature invalid for agg pool"; - "reason" => ?e, - "peer" => %peer_id, + reason = ?e, + %peer_id, + "Sync committee signature invalid for agg pool" ) } @@ -2121,10 +2037,9 @@ impl NetworkBeaconProcessor { .add_contribution_to_block_inclusion_pool(sync_contribution) { debug!( - self.log, - "Sync contribution invalid for op pool"; - "reason" => ?e, - "peer" => %peer_id, + reason = ?e, + %peer_id, + "Sync contribution invalid for op pool" ) } metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_IMPORTED_TOTAL); @@ -2149,10 +2064,9 @@ impl NetworkBeaconProcessor { match e { LightClientFinalityUpdateError::InvalidLightClientFinalityUpdate => { debug!( - self.log, - "Light client invalid finality update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client invalid finality update" ); self.gossip_penalize_peer( @@ -2163,10 +2077,9 @@ impl NetworkBeaconProcessor { } LightClientFinalityUpdateError::TooEarly => { debug!( - self.log, - "Light client finality update too early"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client finality update too early" ); self.gossip_penalize_peer( @@ -2177,10 +2090,9 @@ impl NetworkBeaconProcessor { } LightClientFinalityUpdateError::SigSlotStartIsNone | LightClientFinalityUpdateError::FailedConstructingUpdate => debug!( - self.log, - "Light client error constructing finality update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client error constructing finality update" ), } self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2202,10 +2114,9 @@ impl NetworkBeaconProcessor { ) { Ok(verified_light_client_optimistic_update) => { debug!( - self.log, - "Light client successful optimistic update"; - "peer" => %peer_id, - "parent_root" => %verified_light_client_optimistic_update.parent_root, + %peer_id, + parent_root = %verified_light_client_optimistic_update.parent_root, + "Light client successful optimistic update" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -2217,10 +2128,9 @@ impl NetworkBeaconProcessor { &metrics::BEACON_PROCESSOR_REPROCESSING_QUEUE_SENT_OPTIMISTIC_UPDATES, ); debug!( - self.log, - "Optimistic update for unknown block"; - "peer_id" => %peer_id, - "parent_root" => ?parent_root + %peer_id, + ?parent_root, + "Optimistic update for unknown block" ); if let Some(sender) = reprocess_tx { @@ -2241,17 +2151,13 @@ impl NetworkBeaconProcessor { ); if sender.try_send(msg).is_err() { - error!( - self.log, - "Failed to send optimistic update for re-processing"; - ) + error!("Failed to send optimistic update for re-processing") } } else { debug!( - self.log, - "Not sending light client update because it had been reprocessed"; - "peer_id" => %peer_id, - "parent_root" => ?parent_root + %peer_id, + ?parent_root, + "Not sending light client update because it had been reprocessed" ); self.propagate_validation_result( @@ -2266,10 +2172,9 @@ impl NetworkBeaconProcessor { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client invalid optimistic update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client invalid optimistic update" ); self.gossip_penalize_peer( @@ -2281,10 +2186,9 @@ impl NetworkBeaconProcessor { LightClientOptimisticUpdateError::TooEarly => { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client optimistic update too early"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client optimistic update too early" ); self.gossip_penalize_peer( @@ -2298,10 +2202,9 @@ impl NetworkBeaconProcessor { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client error constructing optimistic update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client error constructing optimistic update" ) } } @@ -2333,11 +2236,10 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Attestation is not within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Attestation is not within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots" ); // Peers that are slow or not to spec can spam us with these messages draining our @@ -2464,11 +2366,10 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Attestation already known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Attestation already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -2481,11 +2382,10 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Aggregator already known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Aggregator already known" ); // This is an allowed behaviour. self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2502,13 +2402,12 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior attestation known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "epoch" => %epoch, - "validator_index" => validator_index, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + %epoch, + validator_index, + ?attestation_type, + "Prior attestation known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2523,11 +2422,10 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Validation Index too high"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Validation Index too high" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2543,12 +2441,11 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Committee index non zero"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, - "committee_index" => index, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + committee_index = index, + "Committee index non zero" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2559,10 +2456,9 @@ impl NetworkBeaconProcessor { } AttnError::UnknownHeadBlock { beacon_block_root } => { trace!( - self.log, - "Attestation for unknown block"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root + %peer_id, + block = ?beacon_block_root, + "Attestation for unknown block" ); if let Some(sender) = reprocess_tx { // We don't know the block, get the sync manager to handle the block lookup, and @@ -2573,11 +2469,7 @@ impl NetworkBeaconProcessor { *beacon_block_root, )) .unwrap_or_else(|_| { - warn!( - self.log, - "Failed to send to sync service"; - "msg" => "UnknownBlockHash" - ) + warn!(msg = "UnknownBlockHash", "Failed to send to sync service") }); let msg = match failed_att { FailedAtt::Aggregate { @@ -2606,9 +2498,8 @@ impl NetworkBeaconProcessor { // for `SingleAttestation`s separately and should not be able to hit // an `UnknownHeadBlock` error. error!( - self.log, - "Dropping SingleAttestation instead of requeueing"; - "block_root" => ?beacon_block_root, + block_root = ?beacon_block_root, + "Dropping SingleAttestation instead of requeueing" ); return; } @@ -2640,10 +2531,7 @@ impl NetworkBeaconProcessor { }; if sender.try_send(msg).is_err() { - error!( - self.log, - "Failed to send attestation for re-processing"; - ) + error!("Failed to send attestation for re-processing") } } else { // We shouldn't make any further attempts to process this attestation. @@ -2754,10 +2642,9 @@ impl NetworkBeaconProcessor { * The attestation was received on an incorrect subnet id. */ debug!( - self.log, - "Received attestation on incorrect subnet"; - "expected" => ?expected, - "received" => ?received, + ?expected, + ?received, + "Received attestation on incorrect subnet" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2815,10 +2702,9 @@ impl NetworkBeaconProcessor { * The message is not necessarily invalid, but we choose to ignore it. */ debug!( - self.log, - "Rejected long skip slot attestation"; - "head_block_slot" => head_block_slot, - "attestation_slot" => attestation_slot, + %head_block_slot, + %attestation_slot, + "Rejected long skip slot attestation" ); // In this case we wish to penalize gossipsub peers that do this to avoid future // attestations that have too many skip slots. @@ -2831,10 +2717,9 @@ impl NetworkBeaconProcessor { } AttnError::HeadBlockFinalized { beacon_block_root } => { debug!( - self.log, - "Ignored attestation to finalized block"; - "block_root" => ?beacon_block_root, - "attestation_slot" => failed_att.attestation_data().slot, + block_root = ?beacon_block_root, + attestation_slot = %failed_att.attestation_data().slot, + "Ignored attestation to finalized block" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2850,19 +2735,18 @@ impl NetworkBeaconProcessor { AttnError::BeaconChainError(BeaconChainError::DBError(Error::HotColdDBError( HotColdDBError::FinalizedStateNotInHotDatabase { .. }, ))) => { - debug!(self.log, "Attestation for finalized state"; "peer_id" => % peer_id); + debug!(%peer_id, "Attestation for finalized state"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } e @ AttnError::BeaconChainError(BeaconChainError::MaxCommitteePromises(_)) => { debug!( - self.log, - "Dropping attestation"; - "target_root" => ?failed_att.attestation_data().target.root, - "beacon_block_root" => ?beacon_block_root, - "slot" => ?failed_att.attestation_data().slot, - "type" => ?attestation_type, - "error" => ?e, - "peer_id" => % peer_id + target_root = ?failed_att.attestation_data().target.root, + ?beacon_block_root, + slot = ?failed_att.attestation_data().slot, + ?attestation_type, + error = ?e, + %peer_id, + "Dropping attestation" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } @@ -2875,25 +2759,23 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate attestation"; - "beacon_block_root" => ?beacon_block_root, - "slot" => ?failed_att.attestation_data().slot, - "type" => ?attestation_type, - "peer_id" => %peer_id, - "error" => ?e, + ?beacon_block_root, + slot = ?failed_att.attestation_data().slot, + ?attestation_type, + %peer_id, + error = ?e, + "Unable to validate attestation" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } } debug!( - self.log, - "Invalid attestation from network"; - "reason" => ?error, - "block" => ?beacon_block_root, - "peer_id" => %peer_id, - "type" => ?attestation_type, + reason = ?error, + block = ?beacon_block_root, + %peer_id, + ?attestation_type, + "Invalid attestation from network" ); } @@ -2919,10 +2801,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots" ); // Unlike attestations, we have a zero slot buffer in case of sync committee messages, @@ -2944,10 +2825,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots" ); // Compute the slot when we received the message. @@ -3037,10 +2917,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Sync committee message is already known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -3053,10 +2932,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Validation Index too high"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Validation Index too high" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3067,10 +2945,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::UnknownValidatorPubkey(_) => { debug!( - self.log, - "Validator pubkey is unknown"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Validator pubkey is unknown" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3084,10 +2961,9 @@ impl NetworkBeaconProcessor { * The sync committee message was received on an incorrect subnet id. */ debug!( - self.log, - "Received sync committee message on incorrect subnet"; - "expected" => ?expected, - "received" => ?received, + ?expected, + ?received, + "Received sync committee message on incorrect subnet" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3116,10 +2992,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior sync committee message known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Prior sync committee message known" ); // Do not penalize the peer. @@ -3135,10 +3010,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior sync contribution message known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Prior sync contribution message known" ); // We still penalize the peer slightly. We don't want this to be a recurring // behaviour. @@ -3161,10 +3035,9 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Unable to validate sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } @@ -3177,10 +3050,9 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Unable to validate sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3192,10 +3064,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::ContributionError(e) => { error!( - self.log, - "Error while processing sync contribution"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Error while processing sync contribution" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3207,10 +3078,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::SyncCommitteeError(e) => { error!( - self.log, - "Error while processing sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Error while processing sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3225,10 +3095,9 @@ impl NetworkBeaconProcessor { This would most likely imply incompatible configs or an invalid message. */ error!( - self.log, - "Arithematic error while processing sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Arithematic error while processing sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); self.gossip_penalize_peer( @@ -3251,11 +3120,10 @@ impl NetworkBeaconProcessor { } } debug!( - self.log, - "Invalid sync committee message from network"; - "reason" => ?error, - "peer_id" => %peer_id, - "type" => ?message_type, + reason = ?error, + %peer_id, + ?message_type, + "Invalid sync committee message from network" ); } @@ -3313,7 +3181,6 @@ impl NetworkBeaconProcessor { block_root: Hash256, block: &SignedBeaconBlock, error: &BlockError, - log: &Logger, ) { if let InvalidBlockStorage::Enabled(base_dir) = invalid_block_storage { let block_path = base_dir.join(format!("{}_{:?}.ssz", block.slot(), block_root)); @@ -3341,20 +3208,18 @@ impl NetworkBeaconProcessor { }); if let Err(e) = write_result { error!( - log, - "Failed to store invalid block/error"; - "error" => e, - "path" => ?path, - "root" => ?block_root, - "slot" => block.slot(), + error = e, + ?path, + ?block_root, + slot = %block.slot(), + "Failed to store invalid block/error" ) } else { info!( - log, - "Stored invalid block/error "; - "path" => ?path, - "root" => ?block_root, - "slot" => block.slot(), + ?path, + ?block_root, + slot = %block.slot(), + "Stored invalid block/error" ) } }; diff --git a/beacon_node/network/src/network_beacon_processor/mod.rs b/beacon_node/network/src/network_beacon_processor/mod.rs index c06a1f6ee3..1329936932 100644 --- a/beacon_node/network/src/network_beacon_processor/mod.rs +++ b/beacon_node/network/src/network_beacon_processor/mod.rs @@ -9,13 +9,11 @@ use beacon_chain::fetch_blobs::{ }; use beacon_chain::observed_data_sidecars::DoNotObserve; use beacon_chain::{ - builder::Witness, eth1_chain::CachingEth1Backend, AvailabilityProcessingStatus, BeaconChain, - BeaconChainTypes, BlockError, NotifyExecutionLayer, + AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, NotifyExecutionLayer, }; use beacon_processor::{ - work_reprocessing_queue::ReprocessQueueMessage, BeaconProcessorChannels, BeaconProcessorSend, - DuplicateCache, GossipAggregatePackage, GossipAttestationPackage, Work, - WorkEvent as BeaconWorkEvent, + work_reprocessing_queue::ReprocessQueueMessage, BeaconProcessorSend, DuplicateCache, + GossipAggregatePackage, GossipAttestationPackage, Work, WorkEvent as BeaconWorkEvent, }; use lighthouse_network::discovery::ConnectionId; use lighthouse_network::rpc::methods::{ @@ -28,15 +26,12 @@ use lighthouse_network::{ Client, MessageId, NetworkGlobals, PeerId, PubsubMessage, }; use rand::prelude::SliceRandom; -use slog::{debug, error, trace, warn, Logger}; -use slot_clock::ManualSlotClock; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use store::MemoryStore; use task_executor::TaskExecutor; -use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::{self, error::TrySendError}; +use tracing::{debug, error, trace, warn, Instrument}; use types::*; pub use sync_methods::ChainSegmentProcessId; @@ -71,7 +66,6 @@ pub struct NetworkBeaconProcessor { pub network_globals: Arc>, pub invalid_block_storage: InvalidBlockStorage, pub executor: TaskExecutor, - pub log: Logger, } // Publish blobs in batches of exponentially increasing size. @@ -79,9 +73,7 @@ const BLOB_PUBLICATION_EXP_FACTOR: usize = 2; impl NetworkBeaconProcessor { fn try_send(&self, event: BeaconWorkEvent) -> Result<(), Error> { - self.beacon_processor_send - .try_send(event) - .map_err(Into::into) + self.beacon_processor_send.try_send(event) } /// Create a new `Work` event for some `SingleAttestation`. @@ -602,10 +594,7 @@ impl NetworkBeaconProcessor { blocks: Vec>, ) -> Result<(), Error> { let is_backfill = matches!(&process_id, ChainSegmentProcessId::BackSyncBatchId { .. }); - debug!(self.log, "Batch sending for process"; - "blocks" => blocks.len(), - "id" => ?process_id, - ); + debug!(blocks = blocks.len(), id = ?process_id, "Batch sending for process"); let processor = self.clone(); let process_fn = async move { @@ -918,10 +907,9 @@ impl NetworkBeaconProcessor { /// /// Creates a log if there is an internal error. pub(crate) fn send_sync_message(&self, message: SyncMessage) { - self.sync_tx.send(message).unwrap_or_else(|e| { - debug!(self.log, "Could not send message to the sync service"; - "error" => %e) - }); + self.sync_tx + .send(message) + .unwrap_or_else(|e| debug!(error = %e, "Could not send message to the sync service")); } /// Send a message to `network_tx`. @@ -929,8 +917,7 @@ impl NetworkBeaconProcessor { /// Creates a log if there is an internal error. fn send_network_message(&self, message: NetworkMessage) { self.network_tx.send(message).unwrap_or_else(|e| { - debug!(self.log, "Could not send message to the network service. Likely shutdown"; - "error" => %e) + debug!(error = %e, "Could not send message to the network service. Likely shutdown") }); } @@ -940,9 +927,14 @@ impl NetworkBeaconProcessor { block_root: Hash256, publish_blobs: bool, ) { + let is_supernode = self.network_globals.is_supernode(); + let self_cloned = self.clone(); let publish_fn = move |blobs_or_data_column| { - if publish_blobs { + // At the moment non supernodes are not required to publish any columns. + // TODO(das): we could experiment with having full nodes publish their custodied + // columns here. + if publish_blobs && is_supernode { match blobs_or_data_column { BlobsOrDataColumns::Blobs(blobs) => { self_cloned.publish_blobs_gradually(blobs, block_root); @@ -960,48 +952,48 @@ impl NetworkBeaconProcessor { block.clone(), publish_fn, ) + .instrument(tracing::info_span!( + "", + service = "fetch_engine_blobs", + block_root = format!("{:?}", block_root) + )) .await { Ok(Some(availability)) => match availability { AvailabilityProcessingStatus::Imported(_) => { debug!( - self.log, - "Block components retrieved from EL"; - "result" => "imported block and custody columns", - "block_root" => %block_root, + result = "imported block and custody columns", + %block_root, + "Block components retrieved from EL" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Still missing blobs after engine blobs processed successfully"; - "block_root" => %block_root, + %block_root, + "Still missing blobs after engine blobs processed successfully" ); } }, Ok(None) => { debug!( - self.log, - "Fetch blobs completed without import"; - "block_root" => %block_root, + %block_root, + "Fetch blobs completed without import" ); } Err(FetchEngineBlobError::BlobProcessingError(BlockError::DuplicateFullyImported( .., ))) => { debug!( - self.log, - "Fetch blobs duplicate import"; - "block_root" => %block_root, + %block_root, + "Fetch blobs duplicate import" ); } Err(e) => { error!( - self.log, - "Error fetching or processing blobs from EL"; - "error" => ?e, - "block_root" => %block_root, + error = ?e, + %block_root, + "Error fetching or processing blobs from EL" ); } } @@ -1017,6 +1009,11 @@ impl NetworkBeaconProcessor { self: &Arc, block_root: Hash256, ) -> Option { + // Only supernodes attempt reconstruction + if !self.network_globals.is_supernode() { + return None; + } + let result = self.chain.reconstruct_data_columns(block_root).await; match result { Ok(Some((availability_processing_status, data_columns_to_publish))) => { @@ -1024,19 +1021,17 @@ impl NetworkBeaconProcessor { match &availability_processing_status { AvailabilityProcessingStatus::Imported(hash) => { debug!( - self.log, - "Block components available via reconstruction"; - "result" => "imported block and custody columns", - "block_hash" => %hash, + result = "imported block and custody columns", + block_hash = %hash, + "Block components available via reconstruction" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Block components still missing block after reconstruction"; - "result" => "imported all custody columns", - "block_hash" => %block_root, + result = "imported all custody columns", + block_hash = %block_root, + "Block components still missing block after reconstruction" ); } } @@ -1046,18 +1041,16 @@ impl NetworkBeaconProcessor { Ok(None) => { // reason is tracked via the `KZG_DATA_COLUMN_RECONSTRUCTION_INCOMPLETE_TOTAL` metric trace!( - self.log, - "Reconstruction not required for block"; - "block_hash" => %block_root, + block_hash = %block_root, + "Reconstruction not required for block" ); None } Err(e) => { error!( - self.log, - "Error during data column reconstruction"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error during data column reconstruction" ); None } @@ -1080,7 +1073,6 @@ impl NetworkBeaconProcessor { self.executor.spawn( async move { let chain = self_clone.chain.clone(); - let log = self_clone.chain.logger(); let publish_fn = |blobs: Vec>>| { self_clone.send_network_message(NetworkMessage::Publish { messages: blobs @@ -1109,9 +1101,8 @@ impl NetworkBeaconProcessor { Err(GossipBlobError::RepeatBlob { .. }) => None, Err(e) => { warn!( - log, - "Previously verified blob is invalid"; - "error" => ?e + error = ?e, + "Previously verified blob is invalid" ); None } @@ -1120,10 +1111,9 @@ impl NetworkBeaconProcessor { if !publishable.is_empty() { debug!( - log, - "Publishing blob batch"; - "publish_count" => publishable.len(), - "block_root" => ?block_root, + publish_count = publishable.len(), + ?block_root, + "Publishing blob batch" ); publish_count += publishable.len(); publish_fn(publishable); @@ -1134,12 +1124,11 @@ impl NetworkBeaconProcessor { } debug!( - log, - "Batch blob publication complete"; - "batch_interval" => blob_publication_batch_interval.as_millis(), - "blob_count" => blob_count, - "published_count" => publish_count, - "block_root" => ?block_root, + batch_interval = blob_publication_batch_interval.as_millis(), + blob_count, + publish_count, + ?block_root, + "Batch blob publication complete" ) }, "gradual_blob_publication", @@ -1162,7 +1151,6 @@ impl NetworkBeaconProcessor { self.executor.spawn( async move { let chain = self_clone.chain.clone(); - let log = self_clone.chain.logger(); let publish_fn = |columns: DataColumnSidecarList| { self_clone.send_network_message(NetworkMessage::Publish { messages: columns @@ -1195,9 +1183,8 @@ impl NetworkBeaconProcessor { Err(GossipDataColumnError::PriorKnown { .. }) => None, Err(e) => { warn!( - log, - "Previously verified data column is invalid"; - "error" => ?e + error = ?e, + "Previously verified data column is invalid" ); None } @@ -1206,10 +1193,9 @@ impl NetworkBeaconProcessor { if !publishable.is_empty() { debug!( - log, - "Publishing data column batch"; - "publish_count" => publishable.len(), - "block_root" => ?block_root, + publish_count = publishable.len(), + ?block_root, + "Publishing data column batch" ); publish_count += publishable.len(); publish_fn(publishable); @@ -1219,13 +1205,12 @@ impl NetworkBeaconProcessor { } debug!( - log, - "Batch data column publishing complete"; - "batch_size" => batch_size, - "batch_interval" => blob_publication_batch_interval.as_millis(), - "data_columns_to_publish_count" => data_columns_to_publish.len(), - "published_count" => publish_count, - "block_root" => ?block_root, + batch_size, + batch_interval = blob_publication_batch_interval.as_millis(), + data_columns_to_publish_count = data_columns_to_publish.len(), + publish_count, + ?block_root, + "Batch data column publishing complete" ) }, "gradual_data_column_publication", @@ -1233,9 +1218,20 @@ impl NetworkBeaconProcessor { } } +#[cfg(test)] +use { + beacon_chain::{builder::Witness, eth1_chain::CachingEth1Backend}, + beacon_processor::BeaconProcessorChannels, + slot_clock::ManualSlotClock, + store::MemoryStore, + tokio::sync::mpsc::UnboundedSender, +}; + +#[cfg(test)] type TestBeaconChainType = Witness, E, MemoryStore, MemoryStore>; +#[cfg(test)] impl NetworkBeaconProcessor> { // Instantiates a mostly non-functional version of `Self` and returns the // event receiver that would normally go to the beacon processor. This is @@ -1246,7 +1242,6 @@ impl NetworkBeaconProcessor> { sync_tx: UnboundedSender>, chain: Arc>>, executor: TaskExecutor, - log: Logger, ) -> (Self, mpsc::Receiver>) { let BeaconProcessorChannels { beacon_processor_tx, @@ -1267,7 +1262,6 @@ impl NetworkBeaconProcessor> { network_globals, invalid_block_storage: InvalidBlockStorage::Disabled, executor, - log, }; (network_beacon_processor, beacon_processor_rx) diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 67a1570275..7beadffc06 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -11,13 +11,13 @@ use lighthouse_network::rpc::methods::{ use lighthouse_network::rpc::*; use lighthouse_network::{PeerId, PeerRequestId, ReportSource, Response, SyncInfo}; use methods::LightClientUpdatesByRangeRequest; -use slog::{debug, error, warn}; use slot_clock::SlotClock; use std::collections::{hash_map::Entry, HashMap}; use std::sync::Arc; use tokio_stream::StreamExt; +use tracing::{debug, error, warn}; use types::blob_sidecar::BlobIdentifier; -use types::{Epoch, EthSpec, FixedBytesExtended, Hash256, Slot}; +use types::{Epoch, EthSpec, Hash256, Slot}; impl NetworkBeaconProcessor { /* Auxiliary functions */ @@ -93,20 +93,42 @@ impl NetworkBeaconProcessor { // current slot. This could be because they are using a different genesis time, or that // their or our system's clock is incorrect. Some("Different system clocks or genesis time".to_string()) - } else if remote.finalized_epoch <= local.finalized_epoch - && remote.finalized_root != Hash256::zero() - && local.finalized_root != Hash256::zero() - && self - .chain - .block_root_at_slot(start_slot(remote.finalized_epoch), WhenSlotSkipped::Prev) - .map(|root_opt| root_opt != Some(remote.finalized_root))? + } else if (remote.finalized_epoch == local.finalized_epoch + && remote.finalized_root == local.finalized_root) + || remote.finalized_root.is_zero() + || local.finalized_root.is_zero() + || remote.finalized_epoch > local.finalized_epoch { - // The remote's finalized epoch is less than or equal to ours, but the block root is - // different to the one in our chain. Therefore, the node is on a different chain and we - // should not communicate with them. - Some("Different finalized chain".to_string()) - } else { + // Fast path. Remote finalized checkpoint is either identical, or genesis, or we are at + // genesis, or they are ahead. In all cases, we should allow this peer to connect to us + // so we can sync from them. None + } else { + // Remote finalized epoch is less than ours. + let remote_finalized_slot = start_slot(remote.finalized_epoch); + if remote_finalized_slot < self.chain.store.get_oldest_block_slot() { + // Peer's finalized checkpoint is older than anything in our DB. We are unlikely + // to be able to help them sync. + Some("Old finality out of range".to_string()) + } else if remote_finalized_slot < self.chain.store.get_split_slot() { + // Peer's finalized slot is in range for a quick block root check in our freezer DB. + // If that block root check fails, reject them as they're on a different finalized + // chain. + if self + .chain + .block_root_at_slot(remote_finalized_slot, WhenSlotSkipped::Prev) + .map(|root_opt| root_opt != Some(remote.finalized_root))? + { + Some("Different finalized chain".to_string()) + } else { + None + } + } else { + // Peer's finality is older than ours, but newer than our split point, making a + // block root check infeasible. This case shouldn't happen particularly often so + // we give the peer the benefit of the doubt and let them connect to us. + None + } }; Ok(irrelevant_reason) @@ -115,7 +137,7 @@ impl NetworkBeaconProcessor { pub fn process_status(&self, peer_id: PeerId, status: StatusMessage) { match self.check_peer_relevance(&status) { Ok(Some(irrelevant_reason)) => { - debug!(self.log, "Handshake Failure"; "peer" => %peer_id, "reason" => irrelevant_reason); + debug!(%peer_id, reason = irrelevant_reason, "Handshake Failure"); self.goodbye_peer(peer_id, GoodbyeReason::IrrelevantNetwork); } Ok(None) => { @@ -127,9 +149,10 @@ impl NetworkBeaconProcessor { }; self.send_sync_message(SyncMessage::AddPeer(peer_id, info)); } - Err(e) => error!(self.log, "Could not process status message"; - "peer" => %peer_id, - "error" => ?e + Err(e) => error!( + %peer_id, + error = ?e, + "Could not process status message" ), } } @@ -172,11 +195,10 @@ impl NetworkBeaconProcessor { ) -> Result<(), (RpcErrorResponse, &'static str)> { let log_results = |peer_id, requested_blocks, send_block_count| { debug!( - self.log, - "BlocksByRoot outgoing response processed"; - "peer" => %peer_id, - "requested" => requested_blocks, - "returned" => %send_block_count + %peer_id, + requested = requested_blocks, + returned = %send_block_count, + "BlocksByRoot outgoing response processed" ); }; @@ -187,7 +209,7 @@ impl NetworkBeaconProcessor { { Ok(block_stream) => block_stream, Err(e) => { - error!(self.log, "Error getting block stream"; "error" => ?e); + error!( error = ?e, "Error getting block stream"); return Err((RpcErrorResponse::ServerError, "Error getting block stream")); } }; @@ -207,18 +229,16 @@ impl NetworkBeaconProcessor { } Ok(None) => { debug!( - self.log, - "Peer requested unknown block"; - "peer" => %peer_id, - "request_root" => ?root + %peer_id, + request_root = ?root, + "Peer requested unknown block" ); } Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => { debug!( - self.log, - "Failed to fetch execution payload for blocks by root request"; - "block_root" => ?root, - "reason" => "execution layer not synced", + block_root = ?root, + reason = "execution layer not synced", + "Failed to fetch execution payload for blocks by root request" ); log_results(peer_id, requested_blocks, send_block_count); return Err(( @@ -228,11 +248,10 @@ impl NetworkBeaconProcessor { } Err(e) => { debug!( - self.log, - "Error fetching block for peer"; - "peer" => %peer_id, - "request_root" => ?root, - "error" => ?e, + ?peer_id, + request_root = ?root, + error = ?e, + "Error fetching block for peer" ); } } @@ -332,23 +351,21 @@ impl NetworkBeaconProcessor { } Err(e) => { debug!( - self.log, - "Error fetching blob for peer"; - "peer" => %peer_id, - "request_root" => ?root, - "error" => ?e, + ?peer_id, + request_root = ?root, + error = ?e, + "Error fetching blob for peer" ); } } } } debug!( - self.log, - "BlobsByRoot outgoing response processed"; - "peer" => %peer_id, - "request_root" => %requested_root, - "request_indices" => ?requested_indices, - "returned" => send_blob_count + %peer_id, + %requested_root, + ?requested_indices, + returned = send_blob_count, + "BlobsByRoot outgoing response processed" ); Ok(()) @@ -408,10 +425,11 @@ impl NetworkBeaconProcessor { Ok(None) => {} // no-op Err(e) => { // TODO(das): lower log level when feature is stabilized - error!(self.log, "Error getting data column"; - "block_root" => ?data_column_id.block_root, - "peer" => %peer_id, - "error" => ?e + error!( + block_root = ?data_column_id.block_root, + %peer_id, + error = ?e, + "Error getting data column" ); return Err((RpcErrorResponse::ServerError, "Error getting data column")); } @@ -419,11 +437,10 @@ impl NetworkBeaconProcessor { } debug!( - self.log, - "Received DataColumnsByRoot Request"; - "peer" => %peer_id, - "request" => ?request.group_by_ordered_block_root(), - "returned" => send_data_column_count + %peer_id, + request = ?request.group_by_ordered_block_root(), + returned = send_data_column_count, + "Received DataColumnsByRoot Request" ); Ok(()) @@ -463,10 +480,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: LightClientUpdatesByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received LightClientUpdatesByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_period" => req.start_period, + debug!( + %peer_id, + count = req.count, + start_period = req.start_period, + "Received LightClientUpdatesByRange Request" ); // Should not send more than max light client updates @@ -484,10 +502,11 @@ impl NetworkBeaconProcessor { { Ok(lc_updates) => lc_updates, Err(e) => { - error!(self.log, "Unable to obtain light client updates"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + peer = %peer_id, + error = ?e, + "Unable to obtain light client updates" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -506,22 +525,20 @@ impl NetworkBeaconProcessor { if lc_updates_sent < req.count as usize { debug!( - self.log, - "LightClientUpdatesByRange outgoing response processed"; - "peer" => %peer_id, - "info" => "Failed to return all requested light client updates. The peer may have requested data ahead of whats currently available", - "start_period" => req.start_period, - "requested" => req.count, - "returned" => lc_updates_sent + peer = %peer_id, + info = "Failed to return all requested light client updates. The peer may have requested data ahead of whats currently available", + start_period = req.start_period, + requested = req.count, + returned = lc_updates_sent, + "LightClientUpdatesByRange outgoing response processed" ); } else { debug!( - self.log, - "LightClientUpdatesByRange outgoing response processed"; - "peer" => %peer_id, - "start_period" => req.start_period, - "requested" => req.count, - "returned" => lc_updates_sent + peer = %peer_id, + start_period = req.start_period, + requested = req.count, + returned = lc_updates_sent, + "LightClientUpdatesByRange outgoing response processed" ); } @@ -549,10 +566,11 @@ impl NetworkBeaconProcessor { "Bootstrap not available".to_string(), )), Err(e) => { - error!(self.log, "Error getting LightClientBootstrap instance"; - "block_root" => ?request.root, - "peer" => %peer_id, - "error" => ?e + error!( + block_root = ?request.root, + %peer_id, + error = ?e, + "Error getting LightClientBootstrap instance" ); Err((RpcErrorResponse::ResourceUnavailable, format!("{:?}", e))) } @@ -653,10 +671,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: BlocksByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received BlocksByRange Request"; - "peer_id" => %peer_id, - "count" => req.count(), - "start_slot" => req.start_slot(), + debug!( + %peer_id, + count = req.count(), + start_slot = %req.start_slot(), + "Received BlocksByRange Request" ); let forwards_block_root_iter = match self @@ -668,17 +687,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -706,10 +727,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Iteration error")); } @@ -726,24 +748,22 @@ impl NetworkBeaconProcessor { let log_results = |req: BlocksByRangeRequest, peer_id, blocks_sent| { if blocks_sent < (*req.count() as usize) { debug!( - self.log, - "BlocksByRange outgoing response processed"; - "peer" => %peer_id, - "msg" => "Failed to return all requested blocks", - "start_slot" => req.start_slot(), - "current_slot" => current_slot, - "requested" => req.count(), - "returned" => blocks_sent + %peer_id, + msg = "Failed to return all requested blocks", + start_slot = %req.start_slot(), + %current_slot, + requested = req.count(), + returned = blocks_sent, + "BlocksByRange outgoing response processed" ); } else { debug!( - self.log, - "BlocksByRange outgoing response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot(), - "current_slot" => current_slot, - "requested" => req.count(), - "returned" => blocks_sent + %peer_id, + start_slot = %req.start_slot(), + %current_slot, + requested = req.count(), + returned = blocks_sent, + "BlocksByRange outgoing response processed" ); } }; @@ -751,7 +771,7 @@ impl NetworkBeaconProcessor { let mut block_stream = match self.chain.get_blocks(block_roots) { Ok(block_stream) => block_stream, Err(e) => { - error!(self.log, "Error getting block stream"; "error" => ?e); + error!(error = ?e, "Error getting block stream"); return Err((RpcErrorResponse::ServerError, "Iterator error")); } }; @@ -777,21 +797,19 @@ impl NetworkBeaconProcessor { } Ok(None) => { error!( - self.log, - "Block in the chain is not in the store"; - "request" => ?req, - "peer" => %peer_id, - "request_root" => ?root + request = ?req, + %peer_id, + request_root = ?root, + "Block in the chain is not in the store" ); log_results(req, peer_id, blocks_sent); return Err((RpcErrorResponse::ServerError, "Database inconsistency")); } Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => { debug!( - self.log, - "Failed to fetch execution payload for blocks by range request"; - "block_root" => ?root, - "reason" => "execution layer not synced", + block_root = ?root, + reason = "execution layer not synced", + "Failed to fetch execution payload for blocks by range request" ); log_results(req, peer_id, blocks_sent); // send the stream terminator @@ -807,18 +825,16 @@ impl NetworkBeaconProcessor { if matches!(**boxed_error, execution_layer::Error::EngineError(_)) ) { warn!( - self.log, - "Error rebuilding payload for peer"; - "info" => "this may occur occasionally when the EE is busy", - "block_root" => ?root, - "error" => ?e, + info = "this may occur occasionally when the EE is busy", + block_root = ?root, + error = ?e, + "Error rebuilding payload for peer" ); } else { error!( - self.log, - "Error fetching block for peer"; - "block_root" => ?root, - "error" => ?e + block_root = ?root, + error = ?e, + "Error fetching block for peer" ); } log_results(req, peer_id, blocks_sent); @@ -866,10 +882,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: BlobsByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received BlobsByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_slot" => req.start_slot, + debug!( + ?peer_id, + count = req.count, + start_slot = req.start_slot, + "Received BlobsByRange Request" ); let request_start_slot = Slot::from(req.start_slot); @@ -877,7 +894,7 @@ impl NetworkBeaconProcessor { let data_availability_boundary_slot = match self.chain.data_availability_boundary() { Some(boundary) => boundary.start_slot(T::EthSpec::slots_per_epoch()), None => { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Err((RpcErrorResponse::InvalidRequest, "Deneb fork is disabled")); } }; @@ -890,11 +907,10 @@ impl NetworkBeaconProcessor { .unwrap_or(data_availability_boundary_slot); if request_start_slot < oldest_blob_slot { debug!( - self.log, - "Range request start slot is older than data availability boundary."; - "requested_slot" => request_start_slot, - "oldest_blob_slot" => oldest_blob_slot, - "data_availability_boundary" => data_availability_boundary_slot + %request_start_slot, + %oldest_blob_slot, + %data_availability_boundary_slot, + "Range request start slot is older than data availability boundary." ); return if data_availability_boundary_slot < oldest_blob_slot { @@ -917,17 +933,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -961,10 +979,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -977,13 +996,12 @@ impl NetworkBeaconProcessor { let log_results = |peer_id, req: BlobsByRangeRequest, blobs_sent| { debug!( - self.log, - "BlobsByRange outgoing response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot, - "current_slot" => current_slot, - "requested" => req.count, - "returned" => blobs_sent + %peer_id, + start_slot = req.start_slot, + %current_slot, + requested = req.count, + returned = blobs_sent, + "BlobsByRange outgoing response processed" ); }; @@ -1006,12 +1024,11 @@ impl NetworkBeaconProcessor { } Err(e) => { error!( - self.log, - "Error fetching blobs block root"; - "request" => ?req, - "peer" => %peer_id, - "block_root" => ?root, - "error" => ?e + request = ?req, + %peer_id, + block_root = ?root, + error = ?e, + "Error fetching blobs block root" ); log_results(peer_id, req, blobs_sent); @@ -1061,10 +1078,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: DataColumnsByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received DataColumnsByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_slot" => req.start_slot, + debug!( + %peer_id, + count = req.count, + start_slot = req.start_slot, + "Received DataColumnsByRange Request" ); // Should not send more than max request data columns @@ -1080,7 +1098,7 @@ impl NetworkBeaconProcessor { let data_availability_boundary_slot = match self.chain.data_availability_boundary() { Some(boundary) => boundary.start_slot(T::EthSpec::slots_per_epoch()), None => { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Err((RpcErrorResponse::InvalidRequest, "Deneb fork is disabled")); } }; @@ -1094,11 +1112,10 @@ impl NetworkBeaconProcessor { if request_start_slot < oldest_data_column_slot { debug!( - self.log, - "Range request start slot is older than data availability boundary."; - "requested_slot" => request_start_slot, - "oldest_data_column_slot" => oldest_data_column_slot, - "data_availability_boundary" => data_availability_boundary_slot + %request_start_slot, + %oldest_data_column_slot, + %data_availability_boundary_slot, + "Range request start slot is older than data availability boundary." ); return if data_availability_boundary_slot < oldest_data_column_slot { @@ -1121,17 +1138,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -1165,10 +1184,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -1195,12 +1215,11 @@ impl NetworkBeaconProcessor { Ok(None) => {} // no-op Err(e) => { error!( - self.log, - "Error fetching data columns block root"; - "request" => ?req, - "peer" => %peer_id, - "block_root" => ?root, - "error" => ?e + request = ?req, + %peer_id, + block_root = ?root, + error = ?e, + "Error fetching data columns block root" ); return Err(( RpcErrorResponse::ServerError, @@ -1217,13 +1236,12 @@ impl NetworkBeaconProcessor { .unwrap_or_else(|_| self.chain.slot_clock.genesis_slot()); debug!( - self.log, - "DataColumnsByRange Response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot, - "current_slot" => current_slot, - "requested" => req.count, - "returned" => data_columns_sent + %peer_id, + start_slot = req.start_slot, + %current_slot, + requested = req.count, + returned = data_columns_sent, + "DataColumnsByRange Response processed" ); Ok(()) diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index f5fe7ee98b..48ae26c826 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -18,11 +18,11 @@ use beacon_processor::{ AsyncFn, BlockingFn, DuplicateCache, }; use lighthouse_network::PeerAction; -use slog::{debug, error, info, warn}; use std::sync::Arc; use std::time::Duration; use store::KzgCommitment; use tokio::sync::mpsc; +use tracing::{debug, error, info, warn}; use types::beacon_block_body::format_kzg_commitments; use types::blob_sidecar::FixedBlobSidecarList; use types::{BlockImportSource, DataColumnSidecar, DataColumnSidecarList, Epoch, Hash256}; @@ -112,11 +112,10 @@ impl NetworkBeaconProcessor { // Check if the block is already being imported through another source let Some(handle) = duplicate_cache.check_and_insert(block_root) else { debug!( - self.log, - "Gossip block is being processed"; - "action" => "sending rpc block to reprocessing queue", - "block_root" => %block_root, - "process_type" => ?process_type, + action = "sending rpc block to reprocessing queue", + %block_root, + ?process_type, + "Gossip block is being processed" ); // Send message to work reprocess queue to retry the block @@ -133,7 +132,7 @@ impl NetworkBeaconProcessor { }); if reprocess_tx.try_send(reprocess_msg).is_err() { - error!(self.log, "Failed to inform block import"; "source" => "rpc", "block_root" => %block_root) + error!(source = "rpc", %block_root,"Failed to inform block import") }; return; }; @@ -144,13 +143,12 @@ impl NetworkBeaconProcessor { let commitments_formatted = block.as_block().commitments_formatted(); debug!( - self.log, - "Processing RPC block"; - "block_root" => ?block_root, - "proposer" => block.message().proposer_index(), - "slot" => block.slot(), - "commitments" => commitments_formatted, - "process_type" => ?process_type, + ?block_root, + proposer = block.message().proposer_index(), + slot = %block.slot(), + commitments_formatted, + ?process_type, + "Processing RPC block" ); let signed_beacon_block = block.block_cloned(); @@ -168,15 +166,22 @@ impl NetworkBeaconProcessor { // RPC block imported, regardless of process type match result.as_ref() { Ok(AvailabilityProcessingStatus::Imported(hash)) => { - info!(self.log, "New RPC block received"; "slot" => slot, "hash" => %hash); - + info!( + %slot, + %hash, + "New RPC block received", + ); // Trigger processing for work referencing this block. let reprocess_msg = ReprocessQueueMessage::BlockImported { block_root: *hash, parent_root, }; if reprocess_tx.try_send(reprocess_msg).is_err() { - error!(self.log, "Failed to inform block import"; "source" => "rpc", "block_root" => %hash) + error!( + source = "rpc", + block_root = %hash, + "Failed to inform block import" + ); }; self.chain.block_times_cache.write().set_time_observed( *hash, @@ -265,12 +270,11 @@ impl NetworkBeaconProcessor { let commitments = format_kzg_commitments(&commitments); debug!( - self.log, - "RPC blobs received"; - "indices" => ?indices, - "block_root" => %block_root, - "slot" => %slot, - "commitments" => commitments, + ?indices, + %block_root, + %slot, + commitments, + "RPC blobs received" ); if let Ok(current_slot) = self.chain.slot() { @@ -290,37 +294,33 @@ impl NetworkBeaconProcessor { match &result { Ok(AvailabilityProcessingStatus::Imported(hash)) => { debug!( - self.log, - "Block components retrieved"; - "result" => "imported block and blobs", - "slot" => %slot, - "block_hash" => %hash, + result = "imported block and blobs", + %slot, + block_hash = %hash, + "Block components retrieved" ); self.chain.recompute_head_at_current_slot().await; } Ok(AvailabilityProcessingStatus::MissingComponents(_, _)) => { debug!( - self.log, - "Missing components over rpc"; - "block_hash" => %block_root, - "slot" => %slot, + block_hash = %block_root, + %slot, + "Missing components over rpc" ); } Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Blobs have already been imported"; - "block_hash" => %block_root, - "slot" => %slot, + block_hash = %block_root, + %slot, + "Blobs have already been imported" ); } Err(e) => { warn!( - self.log, - "Error when importing rpc blobs"; - "error" => ?e, - "block_hash" => %block_root, - "slot" => %slot, + error = ?e, + block_hash = %block_root, + %slot, + "Error when importing rpc blobs" ); } } @@ -354,11 +354,10 @@ impl NetworkBeaconProcessor { let mut indices = custody_columns.iter().map(|d| d.index).collect::>(); indices.sort_unstable(); debug!( - self.log, - "RPC custody data columns received"; - "indices" => ?indices, - "block_root" => %block_root, - "slot" => %slot, + ?indices, + %block_root, + %slot, + "RPC custody data columns received" ); let mut result = self @@ -371,18 +370,16 @@ impl NetworkBeaconProcessor { Ok(availability) => match availability { AvailabilityProcessingStatus::Imported(hash) => { debug!( - self.log, - "Block components retrieved"; - "result" => "imported block and custody columns", - "block_hash" => %hash, + result = "imported block and custody columns", + block_hash = %hash, + "Block components retrieved" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Missing components over rpc"; - "block_hash" => %block_root, + block_hash = %block_root, + "Missing components over rpc" ); // Attempt reconstruction here before notifying sync, to avoid sending out more requests // that we may no longer need. @@ -395,17 +392,15 @@ impl NetworkBeaconProcessor { }, Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Custody columns have already been imported"; - "block_hash" => %block_root, + block_hash = %block_root, + "Custody columns have already been imported" ); } Err(e) => { warn!( - self.log, - "Error when importing rpc custody columns"; - "error" => ?e, - "block_hash" => %block_root, + error = ?e, + block_hash = %block_root, + "Error when importing rpc custody columns" ); } } @@ -455,27 +450,29 @@ impl NetworkBeaconProcessor { .await { (imported_blocks, Ok(_)) => { - debug!(self.log, "Batch processed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "chain" => chain_id, - "last_block_slot" => end_slot, - "processed_blocks" => sent_blocks, - "service"=> "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + chain = chain_id, + last_block_slot = end_slot, + processed_blocks = sent_blocks, + service= "sync", + "Batch processed"); BatchProcessResult::Success { sent_blocks, imported_blocks, } } (imported_blocks, Err(e)) => { - debug!(self.log, "Batch processing failed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "chain" => chain_id, - "last_block_slot" => end_slot, - "imported_blocks" => imported_blocks, - "error" => %e.message, - "service" => "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + chain = chain_id, + last_block_slot = end_slot, + imported_blocks, + error = %e.message, + service = "sync", + "Batch processing failed"); match e.peer_action { Some(penalty) => BatchProcessResult::FaultyFailure { imported_blocks, @@ -502,28 +499,31 @@ impl NetworkBeaconProcessor { match self.process_backfill_blocks(downloaded_blocks) { (imported_blocks, Ok(_)) => { - debug!(self.log, "Backfill batch processed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "keep_execution_payload" => !self.chain.store.get_config().prune_payloads, - "last_block_slot" => end_slot, - "processed_blocks" => sent_blocks, - "processed_blobs" => n_blobs, - "processed_data_columns" => n_data_columns, - "service"=> "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + keep_execution_payload = !self.chain.store.get_config().prune_payloads, + last_block_slot = end_slot, + processed_blocks = sent_blocks, + processed_blobs = n_blobs, + processed_data_columns = n_data_columns, + service= "sync", + "Backfill batch processed"); BatchProcessResult::Success { sent_blocks, imported_blocks, } } (_, Err(e)) => { - debug!(self.log, "Backfill batch processing failed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "last_block_slot" => end_slot, - "processed_blobs" => n_blobs, - "error" => %e.message, - "service" => "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + last_block_slot = end_slot, + processed_blobs = n_blobs, + error = %e.message, + service = "sync", + "Backfill batch processing failed" + ); match e.peer_action { Some(penalty) => BatchProcessResult::FaultyFailure { imported_blocks: 0, @@ -652,11 +652,10 @@ impl NetworkBeaconProcessor { expected_block_root, } => { debug!( - self.log, - "Backfill batch processing error"; - "error" => "mismatched_block_root", - "block_root" => ?block_root, - "expected_root" => ?expected_block_root + error = "mismatched_block_root", + ?block_root, + expected_root = ?expected_block_root, + "Backfill batch processing error" ); // The peer is faulty if they send blocks with bad roots. Some(PeerAction::LowToleranceError) @@ -664,33 +663,30 @@ impl NetworkBeaconProcessor { HistoricalBlockError::InvalidSignature | HistoricalBlockError::SignatureSet(_) => { warn!( - self.log, - "Backfill batch processing error"; - "error" => ?e + error = ?e, + "Backfill batch processing error" ); // The peer is faulty if they bad signatures. Some(PeerAction::LowToleranceError) } HistoricalBlockError::ValidatorPubkeyCacheTimeout => { warn!( - self.log, - "Backfill batch processing error"; - "error" => "pubkey_cache_timeout" + error = "pubkey_cache_timeout", + "Backfill batch processing error" ); // This is an internal error, do not penalize the peer. None } HistoricalBlockError::IndexOutOfBounds => { error!( - self.log, - "Backfill batch OOB error"; - "error" => ?e, + error = ?e, + "Backfill batch OOB error" ); // This should never occur, don't penalize the peer. None } HistoricalBlockError::StoreError(e) => { - warn!(self.log, "Backfill batch processing error"; "error" => ?e); + warn!(error = ?e, "Backfill batch processing error"); // This is an internal error, don't penalize the peer. None } // @@ -733,19 +729,19 @@ impl NetworkBeaconProcessor { if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot { // The block is too far in the future, drop it. warn!( - self.log, "Block is ahead of our slot clock"; - "msg" => "block for future slot rejected, check your time", - "present_slot" => present_slot, - "block_slot" => block_slot, - "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + msg = "block for future slot rejected, check your time", + %present_slot, + %block_slot, + FUTURE_SLOT_TOLERANCE, + "Block is ahead of our slot clock" ); } else { // The block is in the future, but not too far. debug!( - self.log, "Block is slightly ahead of our slot clock. Ignoring."; - "present_slot" => present_slot, - "block_slot" => block_slot, - "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + %present_slot, + %block_slot, + FUTURE_SLOT_TOLERANCE, + "Block is slightly ahead of our slot clock. Ignoring." ); } @@ -759,18 +755,30 @@ impl NetworkBeaconProcessor { }) } BlockError::WouldRevertFinalizedSlot { .. } => { - debug!(self.log, "Finalized or earlier block processed";); + debug!("Finalized or earlier block processed"); Ok(()) } + BlockError::NotFinalizedDescendant { block_parent_root } => { + debug!( + "Not syncing to a chain that conflicts with the canonical or manual finalized checkpoint" + ); + Err(ChainSegmentFailed { + message: format!( + "Block with parent_root {} conflicts with our checkpoint state", + block_parent_root + ), + peer_action: Some(PeerAction::Fatal), + }) + } BlockError::GenesisBlock => { - debug!(self.log, "Genesis block was processed"); + debug!("Genesis block was processed"); Ok(()) } BlockError::BeaconChainError(e) => { warn!( - self.log, "BlockProcessingFailure"; - "msg" => "unexpected condition in processing block.", - "outcome" => ?e, + msg = "unexpected condition in processing block.", + outcome = ?e, + "BlockProcessingFailure" ); Err(ChainSegmentFailed { @@ -783,10 +791,10 @@ impl NetworkBeaconProcessor { if !epe.penalize_peer() { // These errors indicate an issue with the EL and not the `ChainSegment`. // Pause the syncing while the EL recovers - debug!(self.log, - "Execution layer verification failed"; - "outcome" => "pausing sync", - "err" => ?err + debug!( + outcome = "pausing sync", + ?err, + "Execution layer verification failed" ); Err(ChainSegmentFailed { message: format!("Execution layer offline. Reason: {:?}", err), @@ -794,9 +802,9 @@ impl NetworkBeaconProcessor { peer_action: None, }) } else { - debug!(self.log, - "Invalid execution payload"; - "error" => ?err + debug!( + error = ?err, + "Invalid execution payload" ); Err(ChainSegmentFailed { message: format!( @@ -809,10 +817,9 @@ impl NetworkBeaconProcessor { } ref err @ BlockError::ParentExecutionPayloadInvalid { ref parent_root } => { warn!( - self.log, - "Failed to sync chain built on invalid parent"; - "parent_root" => ?parent_root, - "advice" => "check execution node for corruption then restart it and Lighthouse", + ?parent_root, + advice = "check execution node for corruption then restart it and Lighthouse", + "Failed to sync chain built on invalid parent" ); Err(ChainSegmentFailed { message: format!("Peer sent invalid block. Reason: {err:?}"), @@ -822,11 +829,19 @@ impl NetworkBeaconProcessor { peer_action: Some(PeerAction::LowToleranceError), }) } + // Penalise peers for sending us banned blocks. + BlockError::KnownInvalidExecutionPayload(block_root) => { + warn!(?block_root, "Received block known to be invalid",); + Err(ChainSegmentFailed { + message: format!("Banned block: {block_root:?}"), + peer_action: Some(PeerAction::Fatal), + }) + } other => { debug!( - self.log, "Invalid block received"; - "msg" => "peer sent invalid block", - "outcome" => %other, + msg = "peer sent invalid block", + outcome = %other, + "Invalid block received" ); Err(ChainSegmentFailed { diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 8415ece638..69ba5c1dbd 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -182,8 +182,6 @@ impl TestRig { let (network_tx, _network_rx) = mpsc::unbounded_channel(); - let log = harness.logger().clone(); - let beacon_processor_config = BeaconProcessorConfig { enable_backfill_rate_limiting, ..Default::default() @@ -221,7 +219,6 @@ impl TestRig { meta_data, vec![], false, - &log, network_config, spec, )); @@ -241,7 +238,6 @@ impl TestRig { network_globals: network_globals.clone(), invalid_block_storage: InvalidBlockStorage::Disabled, executor: executor.clone(), - log: log.clone(), }; let network_beacon_processor = Arc::new(network_beacon_processor); @@ -250,7 +246,6 @@ impl TestRig { executor, current_workers: 0, config: beacon_processor_config, - log: log.clone(), } .spawn_manager( beacon_processor_rx, diff --git a/beacon_node/network/src/persisted_dht.rs b/beacon_node/network/src/persisted_dht.rs index 1e1420883e..9c112dba86 100644 --- a/beacon_node/network/src/persisted_dht.rs +++ b/beacon_node/network/src/persisted_dht.rs @@ -69,20 +69,17 @@ impl StoreItem for PersistedDht { #[cfg(test)] mod tests { use super::*; - use sloggers::{null::NullLoggerBuilder, Build}; use std::str::FromStr; use store::config::StoreConfig; use store::MemoryStore; use types::{ChainSpec, MinimalEthSpec}; #[test] fn test_persisted_dht() { - let log = NullLoggerBuilder.build().unwrap(); let store: HotColdDB< MinimalEthSpec, MemoryStore, MemoryStore, - > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into(), log) - .unwrap(); + > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into()).unwrap(); let enrs = vec![Enr::from_str("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8").unwrap()]; store .put_item(&DHT_DB_KEY, &PersistedDht { enrs: enrs.clone() }) diff --git a/beacon_node/network/src/router.rs b/beacon_node/network/src/router.rs index 36e5c391e9..7376244501 100644 --- a/beacon_node/network/src/router.rs +++ b/beacon_node/network/src/router.rs @@ -21,13 +21,13 @@ use lighthouse_network::{ service::api_types::{AppRequestId, SyncRequestId}, MessageId, NetworkGlobals, PeerId, PeerRequestId, PubsubMessage, Response, }; +use logging::crit; use logging::TimeLatch; -use slog::{crit, debug, o, trace}; -use slog::{error, warn}; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; +use tracing::{debug, error, info_span, trace, warn, Instrument}; use types::{BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, SignedBeaconBlock}; /// Handles messages from the network and routes them to the appropriate service to be handled. @@ -42,8 +42,6 @@ pub struct Router { network: HandlerNetworkContext, /// A multi-threaded, non-blocking processor for applying messages to the beacon chain. network_beacon_processor: Arc>, - /// The `Router` logger. - log: slog::Logger, /// Provides de-bounce functionality for logging. logger_debounce: TimeLatch, } @@ -91,14 +89,11 @@ impl Router { beacon_processor_send: BeaconProcessorSend, beacon_processor_reprocess_tx: mpsc::Sender, fork_context: Arc, - log: slog::Logger, ) -> Result>, String> { - let message_handler_log = log.new(o!("service"=> "router")); - trace!(message_handler_log, "Service starting"); + trace!("Service starting"); let (handler_send, handler_recv) = mpsc::unbounded_channel(); - let sync_logger = log.new(o!("service"=> "sync")); // generate the message channel let (sync_send, sync_recv) = mpsc::unbounded_channel::>(); @@ -112,7 +107,6 @@ impl Router { network_globals: network_globals.clone(), invalid_block_storage, executor: executor.clone(), - log: log.clone(), }; let network_beacon_processor = Arc::new(network_beacon_processor); @@ -124,7 +118,6 @@ impl Router { network_beacon_processor.clone(), sync_recv, fork_context, - sync_logger, ); // generate the Message handler @@ -132,18 +125,18 @@ impl Router { network_globals, chain: beacon_chain, sync_send, - network: HandlerNetworkContext::new(network_send, log.clone()), + network: HandlerNetworkContext::new(network_send), network_beacon_processor, - log: message_handler_log, logger_debounce: TimeLatch::default(), }; // spawn handler task and move the message handler instance into the spawned thread executor.spawn( async move { - debug!(log, "Network message router started"); + debug!("Network message router started"); UnboundedReceiverStream::new(handler_recv) .for_each(move |msg| future::ready(handler.handle_message(msg))) + .instrument(info_span!("", service = "router")) .await; }, "router", @@ -201,7 +194,7 @@ impl Router { rpc_request: rpc::Request, ) { if !self.network_globals.peers.read().is_connected(&peer_id) { - debug!(self.log, "Dropping request of disconnected peer"; "peer_id" => %peer_id, "request" => ?rpc_request); + debug!( %peer_id, request = ?rpc_request, "Dropping request of disconnected peer"); return; } match rpc_request.r#type { @@ -336,7 +329,7 @@ impl Router { ) { match response { Response::Status(status_message) => { - debug!(self.log, "Received Status Response"; "peer_id" => %peer_id, &status_message); + debug!(%peer_id, ?status_message,"Received Status Response"); self.handle_beacon_processor_send_result( self.network_beacon_processor .send_status_message(peer_id, status_message), @@ -448,7 +441,7 @@ impl Router { ) } PubsubMessage::VoluntaryExit(exit) => { - debug!(self.log, "Received a voluntary exit"; "peer_id" => %peer_id); + debug!(%peer_id, "Received a voluntary exit"); self.handle_beacon_processor_send_result( self.network_beacon_processor .send_gossip_voluntary_exit(message_id, peer_id, exit), @@ -456,9 +449,8 @@ impl Router { } PubsubMessage::ProposerSlashing(proposer_slashing) => { debug!( - self.log, - "Received a proposer slashing"; - "peer_id" => %peer_id + %peer_id, + "Received a proposer slashing" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_proposer_slashing( @@ -470,9 +462,8 @@ impl Router { } PubsubMessage::AttesterSlashing(attester_slashing) => { debug!( - self.log, - "Received a attester slashing"; - "peer_id" => %peer_id + %peer_id, + "Received a attester slashing" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_attester_slashing( @@ -484,9 +475,8 @@ impl Router { } PubsubMessage::SignedContributionAndProof(contribution_and_proof) => { trace!( - self.log, - "Received sync committee aggregate"; - "peer_id" => %peer_id + %peer_id, + "Received sync committee aggregate" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_sync_contribution( @@ -499,9 +489,8 @@ impl Router { } PubsubMessage::SyncCommitteeMessage(sync_committtee_msg) => { trace!( - self.log, - "Received sync committee signature"; - "peer_id" => %peer_id + %peer_id, + "Received sync committee signature" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_sync_signature( @@ -515,9 +504,8 @@ impl Router { } PubsubMessage::LightClientFinalityUpdate(light_client_finality_update) => { trace!( - self.log, - "Received light client finality update"; - "peer_id" => %peer_id + %peer_id, + "Received light client finality update" ); self.handle_beacon_processor_send_result( self.network_beacon_processor @@ -531,9 +519,9 @@ impl Router { } PubsubMessage::LightClientOptimisticUpdate(light_client_optimistic_update) => { trace!( - self.log, - "Received light client optimistic update"; - "peer_id" => %peer_id + %peer_id, + "Received light client optimistic update" + ); self.handle_beacon_processor_send_result( self.network_beacon_processor @@ -559,7 +547,7 @@ impl Router { fn send_status(&mut self, peer_id: PeerId) { let status_message = status_message(&self.chain); - debug!(self.log, "Sending Status Request"; "peer" => %peer_id, &status_message); + debug!(%peer_id, ?status_message, "Sending Status Request"); self.network .send_processor_request(peer_id, RequestType::Status(status_message)); } @@ -567,9 +555,8 @@ impl Router { fn send_to_sync(&mut self, message: SyncMessage) { self.sync_send.send(message).unwrap_or_else(|e| { warn!( - self.log, - "Could not send message to the sync service"; - "error" => %e, + error = %e, + "Could not send message to the sync service" ) }); } @@ -598,7 +585,7 @@ impl Router { request_id: RequestId, status: StatusMessage, ) { - debug!(self.log, "Received Status Request"; "peer_id" => %peer_id, &status); + debug!(%peer_id, ?status, "Received Status Request"); // Say status back. self.network.send_response( @@ -626,20 +613,20 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::BlocksByRange { .. } => id, other => { - crit!(self.log, "BlocksByRange response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlocksByRange response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BBRange requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BBRange requests belong to sync"); return; } }; trace!( - self.log, - "Received BlocksByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received BlocksByRange Response" + ); self.send_to_sync(SyncMessage::RpcBlock { @@ -657,9 +644,8 @@ impl Router { blob_sidecar: Option>>, ) { trace!( - self.log, - "Received BlobsByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received BlobsByRange Response" ); if let AppRequestId::Sync(id) = request_id { @@ -670,10 +656,7 @@ impl Router { seen_timestamp: timestamp_now(), }); } else { - crit!( - self.log, - "All blobs by range responses should belong to sync" - ); + crit!("All blobs by range responses should belong to sync"); } } @@ -688,20 +671,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::SingleBlock { .. } => id, other => { - crit!(self.log, "BlocksByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlocksByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BBRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BBRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received BlocksByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received BlocksByRoot Response" ); self.send_to_sync(SyncMessage::RpcBlock { peer_id, @@ -722,20 +704,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::SingleBlob { .. } => id, other => { - crit!(self.log, "BlobsByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlobsByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BlobsByRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BlobsByRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received BlobsByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received BlobsByRoot Response" ); self.send_to_sync(SyncMessage::RpcBlob { request_id, @@ -756,20 +737,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::DataColumnsByRoot { .. } => id, other => { - crit!(self.log, "DataColumnsByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "DataColumnsByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All DataColumnsByRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All DataColumnsByRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received DataColumnsByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received DataColumnsByRoot Response" ); self.send_to_sync(SyncMessage::RpcDataColumn { request_id, @@ -786,9 +766,8 @@ impl Router { data_column: Option>>, ) { trace!( - self.log, - "Received DataColumnsByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received DataColumnsByRange Response" ); if let AppRequestId::Sync(id) = request_id { @@ -799,10 +778,7 @@ impl Router { seen_timestamp: timestamp_now(), }); } else { - crit!( - self.log, - "All data columns by range responses should belong to sync" - ); + crit!("All data columns by range responses should belong to sync"); } } @@ -818,8 +794,7 @@ impl Router { }; if self.logger_debounce.elapsed() { - error!(&self.log, "Unable to send message to the beacon processor"; - "error" => %e, "type" => work_type) + error!(error = %e, work_type, "Unable to send message to the beacon processor") } } } @@ -831,20 +806,18 @@ impl Router { pub struct HandlerNetworkContext { /// The network channel to relay messages to the Network service. network_send: mpsc::UnboundedSender>, - /// Logger for the `NetworkContext`. - log: slog::Logger, } impl HandlerNetworkContext { - pub fn new(network_send: mpsc::UnboundedSender>, log: slog::Logger) -> Self { - Self { network_send, log } + pub fn new(network_send: mpsc::UnboundedSender>) -> Self { + Self { network_send } } /// Sends a message to the network task. fn inform_network(&mut self, msg: NetworkMessage) { - self.network_send.send(msg).unwrap_or_else( - |e| warn!(self.log, "Could not send message to the network service"; "error" => %e), - ) + self.network_send + .send(msg) + .unwrap_or_else(|e| warn!(error = %e,"Could not send message to the network service")) } /// Sends a request to the network task. diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index e1ef57c6ce..778ac63290 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -10,10 +10,10 @@ use beacon_processor::{work_reprocessing_queue::ReprocessQueueMessage, BeaconPro use futures::channel::mpsc::Sender; use futures::future::OptionFuture; use futures::prelude::*; -use futures::StreamExt; use lighthouse_network::rpc::{RequestId, RequestType}; use lighthouse_network::service::Network; use lighthouse_network::types::GossipKind; +use lighthouse_network::Enr; use lighthouse_network::{prometheus_client::registry::Registry, MessageAcceptance}; use lighthouse_network::{ rpc::{GoodbyeReason, RpcErrorResponse}, @@ -24,7 +24,7 @@ use lighthouse_network::{ types::{core_topics_to_subscribe, GossipEncoding, GossipTopic}, MessageId, NetworkEvent, NetworkGlobals, PeerId, }; -use slog::{crit, debug, error, info, o, trace, warn}; +use logging::crit; use std::collections::BTreeSet; use std::{collections::HashSet, pin::Pin, sync::Arc, time::Duration}; use store::HotColdDB; @@ -32,6 +32,7 @@ use strum::IntoStaticStr; use task_executor::ShutdownReason; use tokio::sync::mpsc; use tokio::time::Sleep; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{ ChainSpec, EthSpec, ForkContext, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, Unsigned, ValidatorSubscription, @@ -101,6 +102,10 @@ pub enum NetworkMessage { reason: GoodbyeReason, source: ReportSource, }, + /// Connect to a trusted peer and try to maintain the connection. + ConnectTrustedPeer(Enr), + /// Disconnect from a trusted peer and remove it from the `trusted_peers` mapping. + DisconnectTrustedPeer(Enr), } /// Messages triggered by validators that may trigger a subscription to a subnet. @@ -189,9 +194,8 @@ pub struct NetworkService { metrics_update: tokio::time::Interval, /// gossipsub_parameter_update timer gossipsub_parameter_update: tokio::time::Interval, - /// The logger for the network service. + /// Provides fork specific info. fork_context: Arc, - log: slog::Logger, } impl NetworkService { @@ -210,30 +214,23 @@ impl NetworkService { ), String, > { - let network_log = executor.log().clone(); // build the channels for external comms let (network_senders, network_receivers) = NetworkSenders::new(); #[cfg(feature = "disable-backfill")] - warn!( - network_log, - "Backfill is disabled. DO NOT RUN IN PRODUCTION" - ); + warn!("Backfill is disabled. DO NOT RUN IN PRODUCTION"); if let (true, false, Some(v4)) = ( config.upnp_enabled, config.disable_discovery, config.listen_addrs().v4(), ) { - let nw = network_log.clone(); let v4 = v4.clone(); executor.spawn( async move { - info!(nw, "UPnP Attempting to initialise routes"); - if let Err(e) = - nat::construct_upnp_mappings(v4.addr, v4.disc_port, nw.clone()).await - { - info!(nw, "Could not UPnP map Discovery port"; "error" => %e); + info!("UPnP Attempting to initialise routes"); + if let Err(e) = nat::construct_upnp_mappings(v4.addr, v4.disc_port).await { + info!(error = %e, "Could not UPnP map Discovery port"); } }, "UPnP", @@ -262,7 +259,7 @@ impl NetworkService { &beacon_chain.spec, )); - debug!(network_log, "Current fork"; "fork_name" => ?fork_context.current_fork()); + debug!(fork_name = ?fork_context.current_fork(), "Current fork"); // construct the libp2p service context let service_context = Context { @@ -274,15 +271,14 @@ impl NetworkService { }; // launch libp2p service - let (mut libp2p, network_globals) = - Network::new(executor.clone(), service_context, &network_log).await?; + let (mut libp2p, network_globals) = Network::new(executor.clone(), service_context).await?; // Repopulate the DHT with stored ENR's if discovery is not disabled. if !config.disable_discovery { let enrs_to_load = load_dht::(store.clone()); debug!( - network_log, - "Loading peers into the routing table"; "peers" => enrs_to_load.len() + peers = enrs_to_load.len(), + "Loading peers into the routing table" ); for enr in enrs_to_load { libp2p.add_enr(enr.clone()); @@ -307,7 +303,6 @@ impl NetworkService { beacon_processor_send, beacon_processor_reprocess_tx, fork_context.clone(), - network_log.clone(), )?; // attestation and sync committee subnet service @@ -315,7 +310,6 @@ impl NetworkService { beacon_chain.clone(), network_globals.local_enr().node_id(), &config, - &network_log, ); // create a timer for updating network metrics @@ -330,7 +324,6 @@ impl NetworkService { } = network_receivers; // create the network service and spawn the task - let network_log = network_log.new(o!("service" => "network")); let network_service = NetworkService { beacon_chain, libp2p, @@ -348,7 +341,6 @@ impl NetworkService { metrics_update, gossipsub_parameter_update, fork_context, - log: network_log, }; Ok((network_service, network_globals, network_senders)) @@ -417,7 +409,7 @@ impl NetworkService { fn send_to_router(&mut self, msg: RouterMessage) { if let Err(mpsc::error::SendError(msg)) = self.router_send.send(msg) { - debug!(self.log, "Failed to send msg to router"; "msg" => ?msg); + debug!(?msg, "Failed to send msg to router"); } } @@ -456,7 +448,7 @@ impl NetworkService { Some(_) = &mut self.next_unsubscribe => { let new_enr_fork_id = self.beacon_chain.enr_fork_id(); self.libp2p.unsubscribe_from_fork_topics_except(new_enr_fork_id.fork_digest); - info!(self.log, "Unsubscribed from old fork topics"); + info!("Unsubscribed from old fork topics"); self.next_unsubscribe = Box::pin(None.into()); } @@ -464,17 +456,17 @@ impl NetworkService { if let Some((fork_name, _)) = self.beacon_chain.duration_to_next_fork() { let fork_version = self.beacon_chain.spec.fork_version_for_name(fork_name); let fork_digest = ChainSpec::compute_fork_digest(fork_version, self.beacon_chain.genesis_validators_root); - info!(self.log, "Subscribing to new fork topics"); + info!("Subscribing to new fork topics"); self.libp2p.subscribe_new_fork_topics(fork_name, fork_digest); self.next_fork_subscriptions = Box::pin(None.into()); } else { - error!(self.log, "Fork subscription scheduled but no fork scheduled"); + error!( "Fork subscription scheduled but no fork scheduled"); } } } } - }; + }.instrument(info_span!("", service = "network")); executor.spawn(service_fut, "network"); } @@ -588,9 +580,8 @@ impl NetworkService { .await .map_err(|e| { warn!( - self.log, - "failed to send a shutdown signal"; - "error" => %e + error = %e, + "failed to send a shutdown signal" ) }); } @@ -645,10 +636,9 @@ impl NetworkService { message_id, validation_result, } => { - trace!(self.log, "Propagating gossipsub message"; - "propagation_peer" => ?propagation_source, - "message_id" => %message_id, - "validation_result" => ?validation_result + trace!( propagation_peer = ?propagation_source, + %message_id, + ?validation_result, "Propagating gossipsub message" ); self.libp2p.report_message_validation_result( &propagation_source, @@ -664,10 +654,9 @@ impl NetworkService { } } debug!( - self.log, - "Sending pubsub messages"; - "count" => messages.len(), - "topics" => ?topic_kinds + count = messages.len(), + topics = ?topic_kinds, + "Sending pubsub messages" ); self.libp2p.publish(messages); } @@ -682,6 +671,12 @@ impl NetworkService { reason, source, } => self.libp2p.goodbye_peer(&peer_id, reason, source), + NetworkMessage::ConnectTrustedPeer(enr) => { + self.libp2p.dial_trusted_peer(enr); + } + NetworkMessage::DisconnectTrustedPeer(enr) => { + self.libp2p.remove_trusted_peer(enr); + } NetworkMessage::SubscribeCoreTopics => { if self.subscribed_core_topics() { return; @@ -696,9 +691,8 @@ impl NetworkService { .await { warn!( - self.log, - "failed to send a shutdown signal"; - "error" => %e + error = %e, + "failed to send a shutdown signal" ) } return; @@ -719,7 +713,7 @@ impl NetworkService { if self.libp2p.subscribe(topic.clone()) { subscribed_topics.push(topic); } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic); + warn!(%topic, "Could not subscribe to topic"); } } } @@ -741,9 +735,8 @@ impl NetworkService { if !subscribed_topics.is_empty() { info!( - self.log, - "Subscribed to topics"; - "topics" => ?subscribed_topics.into_iter().map(|topic| format!("{}", topic)).collect::>() + topics = ?subscribed_topics.into_iter().map(|topic| format!("{}", topic)).collect::>(), + "Subscribed to topics" ); } } @@ -777,19 +770,14 @@ impl NetworkService { .update_gossipsub_parameters(active_validators, slot) .is_err() { - error!( - self.log, - "Failed to update gossipsub parameters"; - "active_validators" => active_validators - ); + error!(active_validators, "Failed to update gossipsub parameters"); } } else { // This scenario will only happen if the caches on the cached canonical head aren't // built. That should never be the case. error!( - self.log, - "Active validator count unavailable"; - "info" => "please report this bug" + info = "please report this bug", + "Active validator count unavailable" ); } } @@ -831,10 +819,9 @@ impl NetworkService { let fork_context = &self.fork_context; if let Some(new_fork_name) = fork_context.from_context_bytes(new_fork_digest) { info!( - self.log, - "Transitioned to new fork"; - "old_fork" => ?fork_context.current_fork(), - "new_fork" => ?new_fork_name, + old_fork = ?fork_context.current_fork(), + new_fork = ?new_fork_name, + "Transitioned to new fork" ); fork_context.update_current_fork(*new_fork_name); @@ -851,13 +838,16 @@ impl NetworkService { self.next_fork_subscriptions = Box::pin(next_fork_subscriptions_delay(&self.beacon_chain).into()); self.next_unsubscribe = Box::pin(Some(tokio::time::sleep(unsubscribe_delay)).into()); - info!(self.log, "Network will unsubscribe from old fork gossip topics in a few epochs"; "remaining_epochs" => UNSUBSCRIBE_DELAY_EPOCHS); + info!( + remaining_epochs = UNSUBSCRIBE_DELAY_EPOCHS, + "Network will unsubscribe from old fork gossip topics in a few epochs" + ); // Remove topic weight from old fork topics to prevent peers that left on the mesh on // old topics from being penalized for not sending us messages. self.libp2p.remove_topic_weight_except(new_fork_digest); } else { - crit!(self.log, "Unknown new enr fork id"; "new_fork_id" => ?new_enr_fork_id); + crit!(new_fork_id = ?new_enr_fork_id, "Unknown new enr fork id"); } } @@ -906,26 +896,18 @@ impl Drop for NetworkService { fn drop(&mut self) { // network thread is terminating let enrs = self.libp2p.enr_entries(); - debug!( - self.log, - "Persisting DHT to store"; - "Number of peers" => enrs.len(), - ); + debug!(number_of_peers = enrs.len(), "Persisting DHT to store"); if let Err(e) = clear_dht::(self.store.clone()) { - error!(self.log, "Failed to clear old DHT entries"; "error" => ?e); + error!(error = ?e, "Failed to clear old DHT entries"); } // Still try to update new entries match persist_dht::(self.store.clone(), enrs) { Err(e) => error!( - self.log, - "Failed to persist DHT on drop"; - "error" => ?e - ), - Ok(_) => info!( - self.log, - "Saved DHT state"; + error = ?e, + "Failed to persist DHT on drop" ), + Ok(_) => info!("Saved DHT state"), } - info!(self.log, "Network service shutdown"); + info!("Network service shutdown"); } } diff --git a/beacon_node/network/src/service/tests.rs b/beacon_node/network/src/service/tests.rs index 32bbfcbcaa..15c3321e94 100644 --- a/beacon_node/network/src/service/tests.rs +++ b/beacon_node/network/src/service/tests.rs @@ -8,8 +8,6 @@ use beacon_processor::{BeaconProcessorChannels, BeaconProcessorConfig}; use futures::StreamExt; use lighthouse_network::types::{GossipEncoding, GossipKind}; use lighthouse_network::{Enr, GossipTopic}; -use slog::{o, Drain, Level, Logger}; -use sloggers::{null::NullLoggerBuilder, Build}; use std::str::FromStr; use std::sync::Arc; use tokio::runtime::Runtime; @@ -21,28 +19,8 @@ impl NetworkService { } } -fn get_logger(actual_log: bool) -> Logger { - if actual_log { - let drain = { - let decorator = slog_term::TermDecorator::new().build(); - let decorator = - logging::AlignedTermDecorator::new(decorator, logging::MAX_MESSAGE_WIDTH); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).chan_size(2048).build(); - drain.filter_level(Level::Debug) - }; - - Logger::root(drain.fuse(), o!()) - } else { - let builder = NullLoggerBuilder; - builder.build().expect("should build logger") - } -} - #[test] fn test_dht_persistence() { - let log = get_logger(false); - let beacon_chain = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() .deterministic_keypairs(8) @@ -60,8 +38,12 @@ fn test_dht_persistence() { let (signal, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = - task_executor::TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = task_executor::TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test-dht-persistence".to_string(), + ); let mut config = NetworkConfig::default(); config.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, 21212, 21212, 21213); @@ -137,8 +119,8 @@ fn test_removing_topic_weight_on_old_topics() { let executor = task_executor::TaskExecutor::new( Arc::downgrade(&runtime), exit, - get_logger(false), shutdown_tx, + "test-removing-topic-weight-on-old-topics".to_string(), ); let mut config = NetworkConfig::default(); diff --git a/beacon_node/network/src/subnet_service/attestation_subnets.rs b/beacon_node/network/src/subnet_service/attestation_subnets.rs new file mode 100644 index 0000000000..dd4724b261 --- /dev/null +++ b/beacon_node/network/src/subnet_service/attestation_subnets.rs @@ -0,0 +1,681 @@ +//! This service keeps track of which shard subnet the beacon node should be subscribed to at any +//! given time. It schedules subscriptions to shard subnets, requests peer discoveries and +//! determines whether attestations should be aggregated and/or passed to the beacon node. + +use super::SubnetServiceMessage; +use std::collections::HashSet; +use std::collections::{HashMap, VecDeque}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use delay_map::{HashMapDelay, HashSetDelay}; +use futures::prelude::*; +use lighthouse_network::{discv5::enr::NodeId, NetworkConfig, Subnet, SubnetDiscovery}; +use slot_clock::SlotClock; +use tracing::{debug, error, info, trace, warn}; +use types::{Attestation, EthSpec, Slot, SubnetId, ValidatorSubscription}; + +use crate::metrics; + +/// The minimum number of slots ahead that we attempt to discover peers for a subscription. If the +/// slot is less than this number, skip the peer discovery process. +/// Subnet discovery query takes at most 30 secs, 2 slots take 24s. +pub(crate) const MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD: u64 = 2; +/// The fraction of a slot that we subscribe to a subnet before the required slot. +/// +/// Currently a whole slot ahead. +const ADVANCE_SUBSCRIBE_SLOT_FRACTION: u32 = 1; + +/// The number of slots after an aggregator duty where we remove the entry from +/// `aggregate_validators_on_subnet` delay map. +const UNSUBSCRIBE_AFTER_AGGREGATOR_DUTY: u32 = 2; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) enum SubscriptionKind { + /// Long lived subscriptions. + /// + /// These have a longer duration and are advertised in our ENR. + LongLived, + /// Short lived subscriptions. + /// + /// Subscribing to these subnets has a short duration and we don't advertise it in our ENR. + ShortLived, +} + +/// A particular subnet at a given slot. +#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy)] +pub struct ExactSubnet { + /// The `SubnetId` associated with this subnet. + pub subnet_id: SubnetId, + /// The `Slot` associated with this subnet. + pub slot: Slot, +} + +pub struct AttestationService { + /// Queued events to return to the driving service. + events: VecDeque, + + /// A reference to the beacon chain to process received attestations. + pub(crate) beacon_chain: Arc>, + + /// Subnets we are currently subscribed to as short lived subscriptions. + /// + /// Once they expire, we unsubscribe from these. + /// We subscribe to subnets when we are an aggregator for an exact subnet. + short_lived_subscriptions: HashMapDelay, + + /// Subnets we are currently subscribed to as long lived subscriptions. + /// + /// We advertise these in our ENR. When these expire, the subnet is removed from our ENR. + /// These are required of all beacon nodes. The exact number is determined by the chain + /// specification. + long_lived_subscriptions: HashSet, + + /// Short lived subscriptions that need to be executed in the future. + scheduled_short_lived_subscriptions: HashSetDelay, + + /// A collection timeouts to track the existence of aggregate validator subscriptions at an + /// `ExactSubnet`. + aggregate_validators_on_subnet: Option>, + + /// The waker for the current thread. + waker: Option, + + /// The discovery mechanism of lighthouse is disabled. + discovery_disabled: bool, + + /// We are always subscribed to all subnets. + subscribe_all_subnets: bool, + + /// Our Discv5 node_id. + node_id: NodeId, + + /// Future used to manage subscribing and unsubscribing from long lived subnets. + next_long_lived_subscription_event: Pin>, + + /// Whether this node is a block proposer-only node. + proposer_only: bool, +} + +impl AttestationService { + /* Public functions */ + + /// Establish the service based on the passed configuration. + pub fn new(beacon_chain: Arc>, node_id: NodeId, config: &NetworkConfig) -> Self { + let slot_duration = beacon_chain.slot_clock.slot_duration(); + + if config.subscribe_all_subnets { + info!("Subscribing to all subnets"); + } else { + info!( + subnets_per_node = beacon_chain.spec.subnets_per_node, + subscription_duration_in_epochs = beacon_chain.spec.epochs_per_subnet_subscription, + "Deterministic long lived subnets enabled" + ); + } + + let track_validators = !config.import_all_attestations; + let aggregate_validators_on_subnet = + track_validators.then(|| HashSetDelay::new(slot_duration)); + let mut service = AttestationService { + events: VecDeque::with_capacity(10), + beacon_chain, + short_lived_subscriptions: HashMapDelay::new(slot_duration), + long_lived_subscriptions: HashSet::default(), + scheduled_short_lived_subscriptions: HashSetDelay::default(), + aggregate_validators_on_subnet, + waker: None, + discovery_disabled: config.disable_discovery, + subscribe_all_subnets: config.subscribe_all_subnets, + node_id, + next_long_lived_subscription_event: { + // Set a dummy sleep. Calculating the current subnet subscriptions will update this + // value with a smarter timing + Box::pin(tokio::time::sleep(Duration::from_secs(1))) + }, + proposer_only: config.proposer_only, + }; + + // If we are not subscribed to all subnets, handle the deterministic set of subnets + if !config.subscribe_all_subnets { + service.recompute_long_lived_subnets(); + } + + service + } + + /// Return count of all currently subscribed subnets (long-lived **and** short-lived). + #[cfg(test)] + pub fn subscription_count(&self) -> usize { + if self.subscribe_all_subnets { + self.beacon_chain.spec.attestation_subnet_count as usize + } else { + let count = self + .short_lived_subscriptions + .keys() + .chain(self.long_lived_subscriptions.iter()) + .collect::>() + .len(); + count + } + } + + /// Returns whether we are subscribed to a subnet for testing purposes. + #[cfg(test)] + pub(crate) fn is_subscribed( + &self, + subnet_id: &SubnetId, + subscription_kind: SubscriptionKind, + ) -> bool { + match subscription_kind { + SubscriptionKind::LongLived => self.long_lived_subscriptions.contains(subnet_id), + SubscriptionKind::ShortLived => self.short_lived_subscriptions.contains_key(subnet_id), + } + } + + #[cfg(test)] + pub(crate) fn long_lived_subscriptions(&self) -> &HashSet { + &self.long_lived_subscriptions + } + + /// Processes a list of validator subscriptions. + /// + /// This will: + /// - Register new validators as being known. + /// - Search for peers for required subnets. + /// - Request subscriptions for subnets on specific slots when required. + /// - Build the timeouts for each of these events. + /// + /// This returns a result simply for the ergonomics of using ?. The result can be + /// safely dropped. + pub fn validator_subscriptions( + &mut self, + subscriptions: impl Iterator, + ) -> Result<(), String> { + // If the node is in a proposer-only state, we ignore all subnet subscriptions. + if self.proposer_only { + return Ok(()); + } + + // Maps each subnet_id subscription to it's highest slot + let mut subnets_to_discover: HashMap = HashMap::new(); + + // Registers the validator with the attestation service. + for subscription in subscriptions { + metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_REQUESTS); + + trace!(?subscription, "Validator subscription"); + + // Compute the subnet that is associated with this subscription + let subnet_id = match SubnetId::compute_subnet::( + subscription.slot, + subscription.attestation_committee_index, + subscription.committee_count_at_slot, + &self.beacon_chain.spec, + ) { + Ok(subnet_id) => subnet_id, + Err(e) => { + warn!( + error = ?e, + "Failed to compute subnet id for validator subscription" + ); + continue; + } + }; + // Ensure each subnet_id inserted into the map has the highest slot as it's value. + // Higher slot corresponds to higher min_ttl in the `SubnetDiscovery` entry. + if let Some(slot) = subnets_to_discover.get(&subnet_id) { + if subscription.slot > *slot { + subnets_to_discover.insert(subnet_id, subscription.slot); + } + } else if !self.discovery_disabled { + subnets_to_discover.insert(subnet_id, subscription.slot); + } + + let exact_subnet = ExactSubnet { + subnet_id, + slot: subscription.slot, + }; + + // Determine if the validator is an aggregator. If so, we subscribe to the subnet and + // if successful add the validator to a mapping of known aggregators for that exact + // subnet. + + if subscription.is_aggregator { + metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_AGGREGATOR_REQUESTS); + if let Err(e) = self.subscribe_to_short_lived_subnet(exact_subnet) { + warn!(error = e, "Subscription to subnet error"); + } else { + trace!(?exact_subnet, "Subscribed to subnet for aggregator duties"); + } + } + } + + // If the discovery mechanism isn't disabled, attempt to set up a peer discovery for the + // required subnets. + if !self.discovery_disabled { + if let Err(e) = self.discover_peers_request( + subnets_to_discover + .into_iter() + .map(|(subnet_id, slot)| ExactSubnet { subnet_id, slot }), + ) { + warn!(error = e, "Discovery lookup request error"); + }; + } + + Ok(()) + } + + fn recompute_long_lived_subnets(&mut self) { + // Ensure the next computation is scheduled even if assigning subnets fails. + let next_subscription_event = self + .recompute_long_lived_subnets_inner() + .unwrap_or_else(|_| self.beacon_chain.slot_clock.slot_duration()); + + debug!("Recomputing deterministic long lived subnets"); + self.next_long_lived_subscription_event = + Box::pin(tokio::time::sleep(next_subscription_event)); + + if let Some(waker) = self.waker.as_ref() { + waker.wake_by_ref(); + } + } + + /// Gets the long lived subnets the node should be subscribed to during the current epoch and + /// the remaining duration for which they remain valid. + fn recompute_long_lived_subnets_inner(&mut self) -> Result { + let current_epoch = self.beacon_chain.epoch().map_err(|e| { + if !self + .beacon_chain + .slot_clock + .is_prior_to_genesis() + .unwrap_or(false) + { + error!(err = ?e,"Failed to get the current epoch from clock") + } + })?; + + let (subnets, next_subscription_epoch) = SubnetId::compute_subnets_for_epoch::( + self.node_id.raw(), + current_epoch, + &self.beacon_chain.spec, + ) + .map_err(|e| error!(err = e, "Could not compute subnets for current epoch"))?; + + let next_subscription_slot = + next_subscription_epoch.start_slot(T::EthSpec::slots_per_epoch()); + let next_subscription_event = self + .beacon_chain + .slot_clock + .duration_to_slot(next_subscription_slot) + .ok_or_else(|| { + error!("Failed to compute duration to next to long lived subscription event") + })?; + + self.update_long_lived_subnets(subnets.collect()); + + Ok(next_subscription_event) + } + + /// Updates the long lived subnets. + /// + /// New subnets are registered as subscribed, removed subnets as unsubscribed and the Enr + /// updated accordingly. + fn update_long_lived_subnets(&mut self, mut subnets: HashSet) { + info!(subnets = ?subnets.iter().collect::>(),"Subscribing to long-lived subnets"); + for subnet in &subnets { + // Add the events for those subnets that are new as long lived subscriptions. + if !self.long_lived_subscriptions.contains(subnet) { + // Check if this subnet is new and send the subscription event if needed. + if !self.short_lived_subscriptions.contains_key(subnet) { + debug!( + ?subnet, + subscription_kind = ?SubscriptionKind::LongLived, + "Subscribing to subnet" + ); + self.queue_event(SubnetServiceMessage::Subscribe(Subnet::Attestation( + *subnet, + ))); + } + self.queue_event(SubnetServiceMessage::EnrAdd(Subnet::Attestation(*subnet))); + if !self.discovery_disabled { + self.queue_event(SubnetServiceMessage::DiscoverPeers(vec![SubnetDiscovery { + subnet: Subnet::Attestation(*subnet), + min_ttl: None, + }])) + } + } + } + + // Update the long_lived_subnets set and check for subnets that are being removed + std::mem::swap(&mut self.long_lived_subscriptions, &mut subnets); + for subnet in subnets { + if !self.long_lived_subscriptions.contains(&subnet) { + self.handle_removed_subnet(subnet, SubscriptionKind::LongLived); + } + } + } + + /// Checks if we have subscribed aggregate validators for the subnet. If not, checks the gossip + /// verification, re-propagates and returns false. + pub fn should_process_attestation( + &self, + subnet: SubnetId, + attestation: &Attestation, + ) -> bool { + // Proposer-only mode does not need to process attestations + if self.proposer_only { + return false; + } + self.aggregate_validators_on_subnet + .as_ref() + .map(|tracked_vals| { + tracked_vals.contains_key(&ExactSubnet { + subnet_id: subnet, + slot: attestation.data().slot, + }) + }) + .unwrap_or(true) + } + + /* Internal private functions */ + + /// Adds an event to the event queue and notifies that this service is ready to be polled + /// again. + fn queue_event(&mut self, ev: SubnetServiceMessage) { + self.events.push_back(ev); + if let Some(waker) = &self.waker { + waker.wake_by_ref() + } + } + /// Checks if there are currently queued discovery requests and the time required to make the + /// request. + /// + /// If there is sufficient time, queues a peer discovery request for all the required subnets. + fn discover_peers_request( + &mut self, + exact_subnets: impl Iterator, + ) -> Result<(), &'static str> { + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let discovery_subnets: Vec = exact_subnets + .filter_map(|exact_subnet| { + // Check if there is enough time to perform a discovery lookup. + if exact_subnet.slot + >= current_slot.saturating_add(MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD) + { + // Send out an event to start looking for peers. + // Require the peer for an additional slot to ensure we keep the peer for the + // duration of the subscription. + let min_ttl = self + .beacon_chain + .slot_clock + .duration_to_slot(exact_subnet.slot + 1) + .map(|duration| std::time::Instant::now() + duration); + Some(SubnetDiscovery { + subnet: Subnet::Attestation(exact_subnet.subnet_id), + min_ttl, + }) + } else { + // We may want to check the global PeerInfo to see estimated timeouts for each + // peer before they can be removed. + warn!( + subnet_id = ?exact_subnet, + "Not enough time for a discovery search" + ); + None + } + }) + .collect(); + + if !discovery_subnets.is_empty() { + self.queue_event(SubnetServiceMessage::DiscoverPeers(discovery_subnets)); + } + Ok(()) + } + + // Subscribes to the subnet if it should be done immediately, or schedules it if required. + fn subscribe_to_short_lived_subnet( + &mut self, + ExactSubnet { subnet_id, slot }: ExactSubnet, + ) -> Result<(), &'static str> { + let slot_duration = self.beacon_chain.slot_clock.slot_duration(); + + // The short time we schedule the subscription before it's actually required. This + // ensures we are subscribed on time, and allows consecutive subscriptions to the same + // subnet to overlap, reducing subnet churn. + let advance_subscription_duration = slot_duration / ADVANCE_SUBSCRIBE_SLOT_FRACTION; + // The time to the required slot. + let time_to_subscription_slot = self + .beacon_chain + .slot_clock + .duration_to_slot(slot) + .unwrap_or_default(); // If this is a past slot we will just get a 0 duration. + + // Calculate how long before we need to subscribe to the subnet. + let time_to_subscription_start = + time_to_subscription_slot.saturating_sub(advance_subscription_duration); + + // The time after a duty slot where we no longer need it in the `aggregate_validators_on_subnet` + // delay map. + let time_to_unsubscribe = + time_to_subscription_slot + UNSUBSCRIBE_AFTER_AGGREGATOR_DUTY * slot_duration; + if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { + tracked_vals.insert_at(ExactSubnet { subnet_id, slot }, time_to_unsubscribe); + } + + // If the subscription should be done in the future, schedule it. Otherwise subscribe + // immediately. + if time_to_subscription_start.is_zero() { + // This is a current or past slot, we subscribe immediately. + self.subscribe_to_short_lived_subnet_immediately(subnet_id, slot + 1)?; + } else { + // This is a future slot, schedule subscribing. + trace!(subnet = ?subnet_id, ?time_to_subscription_start,"Scheduling subnet subscription"); + self.scheduled_short_lived_subscriptions + .insert_at(ExactSubnet { subnet_id, slot }, time_to_subscription_start); + } + + Ok(()) + } + + /* A collection of functions that handle the various timeouts */ + + /// Registers a subnet as subscribed. + /// + /// Checks that the time in which the subscription would end is not in the past. If we are + /// already subscribed, extends the timeout if necessary. If this is a new subscription, we send + /// out the appropriate events. + /// + /// On determinist long lived subnets, this is only used for short lived subscriptions. + fn subscribe_to_short_lived_subnet_immediately( + &mut self, + subnet_id: SubnetId, + end_slot: Slot, + ) -> Result<(), &'static str> { + if self.subscribe_all_subnets { + // Case not handled by this service. + return Ok(()); + } + + let time_to_subscription_end = self + .beacon_chain + .slot_clock + .duration_to_slot(end_slot) + .unwrap_or_default(); + + // First check this is worth doing. + if time_to_subscription_end.is_zero() { + return Err("Time when subscription would end has already passed."); + } + + let subscription_kind = SubscriptionKind::ShortLived; + + // We need to check and add a subscription for the right kind, regardless of the presence + // of the subnet as a subscription of the other kind. This is mainly since long lived + // subscriptions can be removed at any time when a validator goes offline. + + let (subscriptions, already_subscribed_as_other_kind) = ( + &mut self.short_lived_subscriptions, + self.long_lived_subscriptions.contains(&subnet_id), + ); + + match subscriptions.get(&subnet_id) { + Some(current_end_slot) => { + // We are already subscribed. Check if we need to extend the subscription. + if &end_slot > current_end_slot { + trace!( + subnet = ?subnet_id, + prev_end_slot = %current_end_slot, + new_end_slot = %end_slot, + ?subscription_kind, + "Extending subscription to subnet" + ); + subscriptions.insert_at(subnet_id, end_slot, time_to_subscription_end); + } + } + None => { + // This is a new subscription. Add with the corresponding timeout and send the + // notification. + subscriptions.insert_at(subnet_id, end_slot, time_to_subscription_end); + + // Inform of the subscription. + if !already_subscribed_as_other_kind { + debug!( + subnet = ?subnet_id, + %end_slot, + ?subscription_kind, + "Subscribing to subnet" + ); + self.queue_event(SubnetServiceMessage::Subscribe(Subnet::Attestation( + subnet_id, + ))); + } + } + } + + Ok(()) + } + + // Unsubscribes from a subnet that was removed if it does not continue to exist as a + // subscription of the other kind. For long lived subscriptions, it also removes the + // advertisement from our ENR. + fn handle_removed_subnet(&mut self, subnet_id: SubnetId, subscription_kind: SubscriptionKind) { + let exists_in_other_subscriptions = match subscription_kind { + SubscriptionKind::LongLived => self.short_lived_subscriptions.contains_key(&subnet_id), + SubscriptionKind::ShortLived => self.long_lived_subscriptions.contains(&subnet_id), + }; + + if !exists_in_other_subscriptions { + // Subscription no longer exists as short lived or long lived. + debug!( + subnet = ?subnet_id, + ?subscription_kind, + "Unsubscribing from subnet" + ); + self.queue_event(SubnetServiceMessage::Unsubscribe(Subnet::Attestation( + subnet_id, + ))); + } + + if subscription_kind == SubscriptionKind::LongLived { + // Remove from our ENR even if we remain subscribed in other way. + self.queue_event(SubnetServiceMessage::EnrRemove(Subnet::Attestation( + subnet_id, + ))); + } + } +} + +impl Stream for AttestationService { + type Item = SubnetServiceMessage; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // Update the waker if needed. + if let Some(waker) = &self.waker { + if waker.will_wake(cx.waker()) { + self.waker = Some(cx.waker().clone()); + } + } else { + self.waker = Some(cx.waker().clone()); + } + + // Send out any generated events. + if let Some(event) = self.events.pop_front() { + return Poll::Ready(Some(event)); + } + + // If we aren't subscribed to all subnets, handle the deterministic long-lived subnets + if !self.subscribe_all_subnets { + match self.next_long_lived_subscription_event.as_mut().poll(cx) { + Poll::Ready(_) => { + self.recompute_long_lived_subnets(); + // We re-wake the task as there could be other subscriptions to process + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Pending => {} + } + } + + // Process scheduled subscriptions that might be ready, since those can extend a soon to + // expire subscription. + match self.scheduled_short_lived_subscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(ExactSubnet { subnet_id, slot }))) => { + if let Err(e) = + self.subscribe_to_short_lived_subnet_immediately(subnet_id, slot + 1) + { + debug!(subnet = ?subnet_id, err = e,"Failed to subscribe to short lived subnet"); + } + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Ready(Some(Err(e))) => { + error!( + error = e, + "Failed to check for scheduled subnet subscriptions" + ); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // Finally process any expired subscriptions. + match self.short_lived_subscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok((subnet_id, _end_slot)))) => { + self.handle_removed_subnet(subnet_id, SubscriptionKind::ShortLived); + // We re-wake the task as there could be other subscriptions to process + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Ready(Some(Err(e))) => { + error!(error = e, "Failed to check for subnet unsubscription times"); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // Poll to remove entries on expiration, no need to act on expiration events. + if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { + if let Poll::Ready(Some(Err(e))) = tracked_vals.poll_next_unpin(cx) { + error!( + error = e, + "Failed to check for aggregate validator on subnet expirations" + ); + } + } + + Poll::Pending + } +} diff --git a/beacon_node/network/src/subnet_service/mod.rs b/beacon_node/network/src/subnet_service/mod.rs index de90e22254..5340538e52 100644 --- a/beacon_node/network/src/subnet_service/mod.rs +++ b/beacon_node/network/src/subnet_service/mod.rs @@ -14,8 +14,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use delay_map::HashSetDelay; use futures::prelude::*; use lighthouse_network::{discv5::enr::NodeId, NetworkConfig, Subnet, SubnetDiscovery}; -use slog::{debug, error, o, warn}; use slot_clock::SlotClock; +use tracing::{debug, error, info, instrument, warn}; use types::{ AttestationData, EthSpec, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription, @@ -107,27 +107,23 @@ pub struct SubnetService { /// Whether this node is a block proposer-only node. proposer_only: bool, - - /// The logger for the attestation service. - log: slog::Logger, } impl SubnetService { /* Public functions */ /// Establish the service based on the passed configuration. - pub fn new( - beacon_chain: Arc>, - node_id: NodeId, - config: &NetworkConfig, - log: &slog::Logger, - ) -> Self { - let log = log.new(o!("service" => "subnet_service")); - + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] + pub fn new(beacon_chain: Arc>, node_id: NodeId, config: &NetworkConfig) -> Self { let slot_duration = beacon_chain.slot_clock.slot_duration(); if config.subscribe_all_subnets { - slog::info!(log, "Subscribing to all subnets"); + info!("Subscribing to all subnets"); } // Build the list of known permanent subscriptions, so that we know not to subscribe or @@ -194,7 +190,6 @@ impl SubnetService { discovery_disabled: config.disable_discovery, subscribe_all_subnets: config.subscribe_all_subnets, proposer_only: config.proposer_only, - log, } } @@ -233,6 +228,12 @@ impl SubnetService { /// /// This returns a result simply for the ergonomics of using ?. The result can be /// safely dropped. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] pub fn validator_subscriptions(&mut self, subscriptions: impl Iterator) { // If the node is in a proposer-only state, we ignore all subnet subscriptions. if self.proposer_only { @@ -257,9 +258,9 @@ impl SubnetService { ) { Ok(subnet_id) => Subnet::Attestation(subnet_id), Err(e) => { - warn!(self.log, - "Failed to compute subnet id for validator subscription"; - "error" => ?e, + warn!( + error = ?e, + "Failed to compute subnet id for validator subscription" ); continue; } @@ -287,10 +288,7 @@ impl SubnetService { if subscription.is_aggregator { metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_AGGREGATOR_REQUESTS); if let Err(e) = self.subscribe_to_subnet(exact_subnet) { - warn!(self.log, - "Subscription to subnet error"; - "error" => e, - ); + warn!(error = e, "Subscription to subnet error"); } } } @@ -305,10 +303,10 @@ impl SubnetService { ) { Ok(subnet_ids) => subnet_ids, Err(e) => { - warn!(self.log, - "Failed to compute subnet id for sync committee subscription"; - "error" => ?e, - "validator_index" => subscription.validator_index + warn!( + error = ?e, + validator_index = subscription.validator_index, + "Failed to compute subnet id for sync committee subscription" ); continue; } @@ -326,7 +324,11 @@ impl SubnetService { .slot_clock .duration_to_slot(slot_required_until) else { - warn!(self.log, "Subscription to sync subnet error"; "error" => "Unable to determine duration to unsubscription slot", "validator_index" => subscription.validator_index); + warn!( + error = "Unable to determine duration to unsubscription slot", + validator_index = subscription.validator_index, + "Subscription to sync subnet error" + ); continue; }; @@ -337,11 +339,11 @@ impl SubnetService { .now() .unwrap_or(Slot::from(0u64)); warn!( - self.log, - "Sync committee subscription is past expiration"; - "subnet" => ?subnet, - "current_slot" => ?current_slot, - "unsubscribe_slot" => ?slot_required_until, ); + ?subnet, + ?current_slot, + unsubscribe_slot = ?slot_required_until, + "Sync committee subscription is past expiration" + ); continue; } @@ -359,13 +361,19 @@ impl SubnetService { // required subnets. if !self.discovery_disabled { if let Err(e) = self.discover_peers_request(subnets_to_discover.into_iter()) { - warn!(self.log, "Discovery lookup request error"; "error" => e); + warn!(error = e, "Discovery lookup request error"); }; } } /// Checks if we have subscribed aggregate validators for the subnet. If not, checks the gossip /// verification, re-propagates and returns false. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] pub fn should_process_attestation( &self, subnet: Subnet, @@ -390,6 +398,12 @@ impl SubnetService { /// Adds an event to the event queue and notifies that this service is ready to be polled /// again. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn queue_event(&mut self, ev: SubnetServiceMessage) { self.events.push_back(ev); if let Some(waker) = &self.waker { @@ -401,6 +415,11 @@ impl SubnetService { /// /// If there is sufficient time, queues a peer discovery request for all the required subnets. // NOTE: Sending early subscriptions results in early searching for peers on subnets. + #[instrument(parent = None, + level = "info", + name = "subnet_service", + skip_all + )] fn discover_peers_request( &mut self, subnets_to_discover: impl Iterator, @@ -432,9 +451,9 @@ impl SubnetService { } else { // We may want to check the global PeerInfo to see estimated timeouts for each // peer before they can be removed. - warn!(self.log, - "Not enough time for a discovery search"; - "subnet_id" => ?subnet, + warn!( + subnet_id = ?subnet, + "Not enough time for a discovery search" ); None } @@ -448,6 +467,12 @@ impl SubnetService { } // Subscribes to the subnet if it should be done immediately, or schedules it if required. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_subnet( &mut self, ExactSubnet { subnet, slot }: ExactSubnet, @@ -500,6 +525,12 @@ impl SubnetService { } /// Adds a subscription event to the sync subnet. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_sync_subnet( &mut self, subnet: Subnet, @@ -529,7 +560,11 @@ impl SubnetService { self.subscriptions .insert_at(subnet, duration_to_unsubscribe); // We are not currently subscribed and have no waiting subscription, create one - debug!(self.log, "Subscribing to subnet"; "subnet" => ?subnet, "until" => ?slot_required_until); + debug!( + ?subnet, + until = ?slot_required_until, + "Subscribing to subnet" + ); self.events .push_back(SubnetServiceMessage::Subscribe(subnet)); @@ -545,6 +580,12 @@ impl SubnetService { /// Checks that the time in which the subscription would end is not in the past. If we are /// already subscribed, extends the timeout if necessary. If this is a new subscription, we send /// out the appropriate events. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_subnet_immediately( &mut self, subnet: Subnet, @@ -588,9 +629,10 @@ impl SubnetService { .insert_at(subnet, time_to_subscription_end); // Inform of the subscription. - debug!(self.log, "Subscribing to subnet"; - "subnet" => ?subnet, - "end_slot" => end_slot, + debug!( + ?subnet, + %end_slot, + "Subscribing to subnet" ); self.queue_event(SubnetServiceMessage::Subscribe(subnet)); } @@ -599,10 +641,16 @@ impl SubnetService { } // Unsubscribes from a subnet that was removed. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn handle_removed_subnet(&mut self, subnet: Subnet) { if !self.subscriptions.contains_key(&subnet) { // Subscription no longer exists as short lived subnet - debug!(self.log, "Unsubscribing from subnet"; "subnet" => ?subnet); + debug!(?subnet, "Unsubscribing from subnet"); self.queue_event(SubnetServiceMessage::Unsubscribe(subnet)); // If this is a sync subnet, we need to remove it from our ENR. @@ -616,6 +664,12 @@ impl SubnetService { impl Stream for SubnetService { type Item = SubnetServiceMessage; + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Update the waker if needed. if let Some(waker) = &self.waker { @@ -639,7 +693,11 @@ impl Stream for SubnetService { // Set the `end_slot` for the subscription to be `duty.slot + 1` so that we unsubscribe // only at the end of the duty slot. if let Err(e) = self.subscribe_to_subnet_immediately(subnet, slot + 1) { - debug!(self.log, "Failed to subscribe to short lived subnet"; "subnet" => ?subnet, "err" => e); + debug!( + subnet = ?subnet, + err = e, + "Failed to subscribe to short lived subnet" + ); } self.waker .as_ref() @@ -647,7 +705,10 @@ impl Stream for SubnetService { .wake_by_ref(); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for scheduled subnet subscriptions"; "error"=> e); + error!( + error = e, + "Failed to check for scheduled subnet subscriptions" + ); } Poll::Ready(None) | Poll::Pending => {} } @@ -663,7 +724,7 @@ impl Stream for SubnetService { .wake_by_ref(); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for subnet unsubscription times"; "error"=> e); + error!(error = e, "Failed to check for subnet unsubscription times"); } Poll::Ready(None) | Poll::Pending => {} } @@ -671,7 +732,10 @@ impl Stream for SubnetService { // Poll to remove entries on expiration, no need to act on expiration events. if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { if let Poll::Ready(Some(Err(e))) = tracked_vals.poll_next_unpin(cx) { - error!(self.log, "Failed to check for aggregate validator on subnet expirations"; "error"=> e); + error!( + error = e, + "Failed to check for aggregate validator on subnet expirations" + ); } } diff --git a/beacon_node/network/src/subnet_service/sync_subnets.rs b/beacon_node/network/src/subnet_service/sync_subnets.rs new file mode 100644 index 0000000000..59ec278a95 --- /dev/null +++ b/beacon_node/network/src/subnet_service/sync_subnets.rs @@ -0,0 +1,345 @@ +//! This service keeps track of which sync committee subnet the beacon node should be subscribed to at any +//! given time. It schedules subscriptions to sync committee subnets and requests peer discoveries. + +use std::collections::{hash_map::Entry, HashMap, VecDeque}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use futures::prelude::*; +use tracing::{debug, error, trace, warn}; + +use super::SubnetServiceMessage; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use delay_map::HashSetDelay; +use lighthouse_network::{NetworkConfig, Subnet, SubnetDiscovery}; +use slot_clock::SlotClock; +use types::{Epoch, EthSpec, SyncCommitteeSubscription, SyncSubnetId}; + +use crate::metrics; + +/// The minimum number of slots ahead that we attempt to discover peers for a subscription. If the +/// slot is less than this number, skip the peer discovery process. +/// Subnet discovery query takes at most 30 secs, 2 slots take 24s. +const MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD: u64 = 2; + +/// A particular subnet at a given slot. +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub struct ExactSubnet { + /// The `SyncSubnetId` associated with this subnet. + pub subnet_id: SyncSubnetId, + /// The epoch until which we need to stay subscribed to the subnet. + pub until_epoch: Epoch, +} +pub struct SyncCommitteeService { + /// Queued events to return to the driving service. + events: VecDeque, + + /// A reference to the beacon chain to process received attestations. + pub(crate) beacon_chain: Arc>, + + /// The collection of all currently subscribed subnets. + subscriptions: HashMap, + + /// A collection of timeouts for when to unsubscribe from a subnet. + unsubscriptions: HashSetDelay, + + /// The waker for the current thread. + waker: Option, + + /// The discovery mechanism of lighthouse is disabled. + discovery_disabled: bool, + + /// We are always subscribed to all subnets. + subscribe_all_subnets: bool, + + /// Whether this node is a block proposer-only node. + proposer_only: bool, +} + +impl SyncCommitteeService { + /* Public functions */ + + pub fn new(beacon_chain: Arc>, config: &NetworkConfig) -> Self { + let spec = &beacon_chain.spec; + let epoch_duration_secs = + beacon_chain.slot_clock.slot_duration().as_secs() * T::EthSpec::slots_per_epoch(); + let default_timeout = + epoch_duration_secs.saturating_mul(spec.epochs_per_sync_committee_period.as_u64()); + + SyncCommitteeService { + events: VecDeque::with_capacity(10), + beacon_chain, + subscriptions: HashMap::new(), + unsubscriptions: HashSetDelay::new(Duration::from_secs(default_timeout)), + waker: None, + subscribe_all_subnets: config.subscribe_all_subnets, + discovery_disabled: config.disable_discovery, + proposer_only: config.proposer_only, + } + } + + /// Return count of all currently subscribed subnets. + #[cfg(test)] + pub fn subscription_count(&self) -> usize { + use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; + if self.subscribe_all_subnets { + SYNC_COMMITTEE_SUBNET_COUNT as usize + } else { + self.subscriptions.len() + } + } + + /// Processes a list of sync committee subscriptions. + /// + /// This will: + /// - Search for peers for required subnets. + /// - Request subscriptions required subnets. + /// - Build the timeouts for each of these events. + /// + /// This returns a result simply for the ergonomics of using ?. The result can be + /// safely dropped. + pub fn validator_subscriptions( + &mut self, + subscriptions: Vec, + ) -> Result<(), String> { + // A proposer-only node does not subscribe to any sync-committees + if self.proposer_only { + return Ok(()); + } + + let mut subnets_to_discover = Vec::new(); + for subscription in subscriptions { + metrics::inc_counter(&metrics::SYNC_COMMITTEE_SUBSCRIPTION_REQUESTS); + //NOTE: We assume all subscriptions have been verified before reaching this service + + // Registers the validator with the subnet service. + // This will subscribe to long-lived random subnets if required. + trace!(?subscription, "Sync committee subscription"); + + let subnet_ids = match SyncSubnetId::compute_subnets_for_sync_committee::( + &subscription.sync_committee_indices, + ) { + Ok(subnet_ids) => subnet_ids, + Err(e) => { + warn!( + error = ?e, + validator_index = subscription.validator_index, + "Failed to compute subnet id for sync committee subscription" + ); + continue; + } + }; + + for subnet_id in subnet_ids { + let exact_subnet = ExactSubnet { + subnet_id, + until_epoch: subscription.until_epoch, + }; + subnets_to_discover.push(exact_subnet.clone()); + if let Err(e) = self.subscribe_to_subnet(exact_subnet.clone()) { + warn!( + error = e, + validator_index = subscription.validator_index, + "Subscription to sync subnet error" + ); + } else { + trace!( + ?exact_subnet, + validator_index = subscription.validator_index, + "Subscribed to subnet for sync committee duties" + ); + } + } + } + // If the discovery mechanism isn't disabled, attempt to set up a peer discovery for the + // required subnets. + if !self.discovery_disabled { + if let Err(e) = self.discover_peers_request(subnets_to_discover.iter()) { + warn!(error = e, "Discovery lookup request error"); + }; + } + + // pre-emptively wake the thread to check for new events + if let Some(waker) = &self.waker { + waker.wake_by_ref(); + } + Ok(()) + } + + /* Internal private functions */ + + /// Checks if there are currently queued discovery requests and the time required to make the + /// request. + /// + /// If there is sufficient time, queues a peer discovery request for all the required subnets. + fn discover_peers_request<'a>( + &mut self, + exact_subnets: impl Iterator, + ) -> Result<(), &'static str> { + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let slots_per_epoch = T::EthSpec::slots_per_epoch(); + + let discovery_subnets: Vec = exact_subnets + .filter_map(|exact_subnet| { + let until_slot = exact_subnet.until_epoch.end_slot(slots_per_epoch); + // check if there is enough time to perform a discovery lookup + if until_slot >= current_slot.saturating_add(MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD) { + // if the slot is more than epoch away, add an event to start looking for peers + // add one slot to ensure we keep the peer for the subscription slot + let min_ttl = self + .beacon_chain + .slot_clock + .duration_to_slot(until_slot + 1) + .map(|duration| std::time::Instant::now() + duration); + Some(SubnetDiscovery { + subnet: Subnet::SyncCommittee(exact_subnet.subnet_id), + min_ttl, + }) + } else { + // We may want to check the global PeerInfo to see estimated timeouts for each + // peer before they can be removed. + warn!( + subnet_id = ?exact_subnet, + "Not enough time for a discovery search" + ); + None + } + }) + .collect(); + + if !discovery_subnets.is_empty() { + self.events + .push_back(SubnetServiceMessage::DiscoverPeers(discovery_subnets)); + } + Ok(()) + } + + /// Adds a subscription event and an associated unsubscription event if required. + fn subscribe_to_subnet(&mut self, exact_subnet: ExactSubnet) -> Result<(), &'static str> { + // Return if we have subscribed to all subnets + if self.subscribe_all_subnets { + return Ok(()); + } + + // Return if we already have a subscription for exact_subnet + if self.subscriptions.get(&exact_subnet.subnet_id) == Some(&exact_subnet.until_epoch) { + return Ok(()); + } + + // Return if we already have subscription set to expire later than the current request. + if let Some(until_epoch) = self.subscriptions.get(&exact_subnet.subnet_id) { + if *until_epoch >= exact_subnet.until_epoch { + return Ok(()); + } + } + + // initialise timing variables + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let slots_per_epoch = T::EthSpec::slots_per_epoch(); + let until_slot = exact_subnet.until_epoch.end_slot(slots_per_epoch); + // Calculate the duration to the unsubscription event. + let expected_end_subscription_duration = if current_slot >= until_slot { + warn!( + %current_slot, + ?exact_subnet, + "Sync committee subscription is past expiration" + ); + return Ok(()); + } else { + let slot_duration = self.beacon_chain.slot_clock.slot_duration(); + + // the duration until we no longer need this subscription. We assume a single slot is + // sufficient. + self.beacon_chain + .slot_clock + .duration_to_slot(until_slot) + .ok_or("Unable to determine duration to unsubscription slot")? + + slot_duration + }; + + if let Entry::Vacant(e) = self.subscriptions.entry(exact_subnet.subnet_id) { + // We are not currently subscribed and have no waiting subscription, create one + debug!(subnet = *exact_subnet.subnet_id, until_epoch = ?exact_subnet.until_epoch, "Subscribing to subnet"); + e.insert(exact_subnet.until_epoch); + self.events + .push_back(SubnetServiceMessage::Subscribe(Subnet::SyncCommittee( + exact_subnet.subnet_id, + ))); + + // add the subnet to the ENR bitfield + self.events + .push_back(SubnetServiceMessage::EnrAdd(Subnet::SyncCommittee( + exact_subnet.subnet_id, + ))); + + // add an unsubscription event to remove ourselves from the subnet once completed + self.unsubscriptions + .insert_at(exact_subnet.subnet_id, expected_end_subscription_duration); + } else { + // We are already subscribed, extend the unsubscription duration + self.unsubscriptions + .update_timeout(&exact_subnet.subnet_id, expected_end_subscription_duration); + } + + Ok(()) + } + + /// A queued unsubscription is ready. + fn handle_unsubscriptions(&mut self, subnet_id: SyncSubnetId) { + debug!(subnet = *subnet_id, "Unsubscribing from subnet"); + + self.subscriptions.remove(&subnet_id); + self.events + .push_back(SubnetServiceMessage::Unsubscribe(Subnet::SyncCommittee( + subnet_id, + ))); + + self.events + .push_back(SubnetServiceMessage::EnrRemove(Subnet::SyncCommittee( + subnet_id, + ))); + } +} + +impl Stream for SyncCommitteeService { + type Item = SubnetServiceMessage; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // update the waker if needed + if let Some(waker) = &self.waker { + if waker.will_wake(cx.waker()) { + self.waker = Some(cx.waker().clone()); + } + } else { + self.waker = Some(cx.waker().clone()); + } + + // process any un-subscription events + match self.unsubscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(exact_subnet))) => self.handle_unsubscriptions(exact_subnet), + Poll::Ready(Some(Err(e))) => { + error!(error = e, "Failed to check for subnet unsubscription times"); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // process any generated events + if let Some(event) = self.events.pop_front() { + return Poll::Ready(Some(event)); + } + + Poll::Pending + } +} diff --git a/beacon_node/network/src/subnet_service/tests/mod.rs b/beacon_node/network/src/subnet_service/tests/mod.rs index 0f3343df63..7e274850b5 100644 --- a/beacon_node/network/src/subnet_service/tests/mod.rs +++ b/beacon_node/network/src/subnet_service/tests/mod.rs @@ -13,6 +13,7 @@ use std::time::{Duration, SystemTime}; use store::config::StoreConfig; use store::{HotColdDB, MemoryStore}; use task_executor::test_utils::TestRuntime; +use tracing_subscriber::EnvFilter; use types::{ CommitteeIndex, Epoch, EthSpec, Hash256, MainnetEthSpec, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription, @@ -20,6 +21,8 @@ use types::{ const SLOT_DURATION_MILLIS: u64 = 400; +const TEST_LOG_LEVEL: Option<&str> = None; + type TestBeaconChainType = Witness< SystemTimeSlotClock, CachingEth1Backend, @@ -37,11 +40,11 @@ impl TestBeaconChain { pub fn new_with_system_clock() -> Self { let spec = Arc::new(MainnetEthSpec::default_spec()); + get_tracing_subscriber(TEST_LOG_LEVEL); + let keypairs = generate_deterministic_keypairs(1); - let log = logging::test_logger(); - let store = - HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone(), log.clone()).unwrap(); + let store = HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone()).unwrap(); let kzg = get_kzg(&spec); @@ -51,7 +54,6 @@ impl TestBeaconChain { let chain = Arc::new( BeaconChainBuilder::new(MainnetEthSpec, kzg.clone()) - .logger(log.clone()) .custom_spec(spec.clone()) .store(Arc::new(store)) .task_executor(test_runtime.task_executor.clone()) @@ -91,10 +93,18 @@ pub fn recent_genesis_time() -> u64 { .as_secs() } +fn get_tracing_subscriber(log_level: Option<&str>) { + if let Some(level) = log_level { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new(level).unwrap()) + .try_init() + .unwrap(); + } +} + static CHAIN: LazyLock = LazyLock::new(TestBeaconChain::new_with_system_clock); fn get_subnet_service() -> SubnetService { - let log = logging::test_logger(); let config = NetworkConfig::default(); let beacon_chain = CHAIN.chain.clone(); @@ -103,7 +113,6 @@ fn get_subnet_service() -> SubnetService { beacon_chain, lighthouse_network::discv5::enr::NodeId::random(), &config, - &log, ) } diff --git a/beacon_node/network/src/sync/backfill_sync/mod.rs b/beacon_node/network/src/sync/backfill_sync/mod.rs index 4220f85fc3..509caf7316 100644 --- a/beacon_node/network/src/sync/backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/backfill_sync/mod.rs @@ -10,8 +10,7 @@ use crate::network_beacon_processor::ChainSegmentProcessId; use crate::sync::manager::BatchProcessResult; -use crate::sync::network_context::RangeRequestId; -use crate::sync::network_context::SyncNetworkContext; +use crate::sync::network_context::{RangeRequestId, RpcResponseError, SyncNetworkContext}; use crate::sync::range_sync::{ BatchConfig, BatchId, BatchInfo, BatchOperationOutcome, BatchProcessingResult, BatchState, }; @@ -20,13 +19,14 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::service::api_types::Id; use lighthouse_network::types::{BackFillState, NetworkGlobals}; use lighthouse_network::{PeerAction, PeerId}; +use logging::crit; use rand::seq::SliceRandom; -use slog::{crit, debug, error, info, warn}; use std::collections::{ btree_map::{BTreeMap, Entry}, HashMap, HashSet, }; use std::sync::Arc; +use tracing::{debug, error, info, instrument, warn}; use types::{Epoch, EthSpec}; /// Blocks are downloaded in batches from peers. This constant specifies how many epochs worth of @@ -146,16 +146,17 @@ pub struct BackFillSync { /// Reference to the network globals in order to obtain valid peers to backfill blocks from /// (i.e synced peers). network_globals: Arc>, - - /// A logger for backfill sync. - log: slog::Logger, } impl BackFillSync { + #[instrument(parent = None, + level = "info", + name = "backfill_sync", + skip_all + )] pub fn new( beacon_chain: Arc>, network_globals: Arc>, - log: slog::Logger, ) -> Self { // Determine if backfill is enabled or not. // If, for some reason a backfill has already been completed (or we've used a trusted @@ -186,7 +187,6 @@ impl BackFillSync { participating_peers: HashSet::new(), restart_failed_sync: false, beacon_chain, - log, }; // Update the global network state with the current backfill state. @@ -195,9 +195,15 @@ impl BackFillSync { } /// Pauses the backfill sync if it's currently syncing. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn pause(&mut self) { if let BackFillState::Syncing = self.state() { - debug!(self.log, "Backfill sync paused"; "processed_epochs" => self.validated_batches, "to_be_processed" => self.current_start); + debug!(processed_epochs = %self.validated_batches, to_be_processed = %self.current_start,"Backfill sync paused"); self.set_state(BackFillState::Paused); } } @@ -206,6 +212,12 @@ impl BackFillSync { /// /// If resuming is successful, reports back the current syncing metrics. #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn start( &mut self, network: &mut SyncNetworkContext, @@ -222,7 +234,7 @@ impl BackFillSync { .is_some() { // If there are peers to resume with, begin the resume. - debug!(self.log, "Resuming backfill sync"; "start_epoch" => self.current_start, "awaiting_batches" => self.batches.len(), "processing_target" => self.processing_target); + debug!(start_epoch = ?self.current_start, awaiting_batches = self.batches.len(), processing_target = ?self.processing_target, "Resuming backfill sync"); self.set_state(BackFillState::Syncing); // Resume any previously failed batches. self.resume_batches(network)?; @@ -251,14 +263,14 @@ impl BackFillSync { // This infallible match exists to force us to update this code if a future // refactor of `ResetEpochError` adds a variant. let ResetEpochError::SyncCompleted = e; - error!(self.log, "Backfill sync completed whilst in failed status"); + error!("Backfill sync completed whilst in failed status"); self.set_state(BackFillState::Completed); return Err(BackFillError::InvalidSyncState(String::from( "chain completed", ))); } - debug!(self.log, "Resuming a failed backfill sync"; "start_epoch" => self.current_start); + debug!(start_epoch = %self.current_start, "Resuming a failed backfill sync"); // begin requesting blocks from the peer pool, until all peers are exhausted. self.request_batches(network)?; @@ -281,6 +293,12 @@ impl BackFillSync { /// A fully synced peer has joined us. /// If we are in a failed state, update a local variable to indicate we are able to restart /// the failed sync on the next attempt. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn fully_synced_peer_joined(&mut self) { if matches!(self.state(), BackFillState::Failed) { self.restart_failed_sync = true; @@ -289,6 +307,12 @@ impl BackFillSync { /// A peer has disconnected. /// If the peer has active batches, those are considered failed and re-requested. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn peer_disconnected( &mut self, @@ -318,15 +342,13 @@ impl BackFillSync { // short circuit early. if self.retry_batch_download(network, id).is_err() { debug!( - self.log, - "Batch could not be retried"; - "batch_id" => id, - "error" => "no synced peers" + batch_id = %id, + error = "no synced peers", + "Batch could not be retried" ); } } else { - debug!(self.log, "Batch not found while removing peer"; - "peer" => %peer_id, "batch" => id) + debug!(peer = %peer_id, batch = %id, "Batch not found while removing peer"); } } } @@ -339,6 +361,12 @@ impl BackFillSync { /// An RPC error has occurred. /// /// If the batch exists it is re-requested. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn inject_error( &mut self, @@ -346,6 +374,7 @@ impl BackFillSync { batch_id: BatchId, peer_id: &PeerId, request_id: Id, + err: RpcResponseError, ) -> Result<(), BackFillError> { if let Some(batch) = self.batches.get_mut(&batch_id) { // A batch could be retried without the peer failing the request (disconnecting/ @@ -356,7 +385,7 @@ impl BackFillSync { if !batch.is_expecting_block(&request_id) { return Ok(()); } - debug!(self.log, "Batch failed"; "batch_epoch" => batch_id, "error" => "rpc_error"); + debug!(batch_epoch = %batch_id, error = ?err, "Batch download failed"); if let Some(active_requests) = self.active_requests.get_mut(peer_id) { active_requests.remove(&batch_id); } @@ -378,6 +407,12 @@ impl BackFillSync { /// If this returns an error, the backfill sync has failed and will be restarted once new peers /// join the system. /// The sync manager should update the global sync state on failure. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn on_block_response( &mut self, @@ -391,7 +426,7 @@ impl BackFillSync { let Some(batch) = self.batches.get_mut(&batch_id) else { if !matches!(self.state(), BackFillState::Failed) { // A batch might get removed when the chain advances, so this is non fatal. - debug!(self.log, "Received a block for unknown batch"; "epoch" => batch_id); + debug!(epoch = %batch_id, "Received a block for unknown batch"); } return Ok(ProcessResult::Successful); }; @@ -416,7 +451,12 @@ impl BackFillSync { Ok(received) => { let awaiting_batches = self.processing_target.saturating_sub(batch_id) / BACKFILL_EPOCHS_PER_BATCH; - debug!(self.log, "Completed batch received"; "epoch" => batch_id, "blocks" => received, "awaiting_batches" => awaiting_batches); + debug!( + epoch = %batch_id, + blocks = received, + %awaiting_batches, + "Completed batch received" + ); // pre-emptively request more blocks from peers whilst we process current blocks, self.request_batches(network)?; @@ -432,6 +472,12 @@ impl BackFillSync { /// The syncing process has failed. /// /// This resets past variables, to allow for a fresh start when resuming. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn fail_sync(&mut self, error: BackFillError) -> Result<(), BackFillError> { // Some errors shouldn't fail the chain. if matches!(error, BackFillError::Paused) { @@ -455,7 +501,7 @@ impl BackFillSync { // NOTE: Lets keep validated_batches for posterity // Emit the log here - error!(self.log, "Backfill sync failed"; "error" => ?error); + error!(?error, "Backfill sync failed"); // Return the error, kinda weird pattern, but I want to use // `self.fail_chain(_)?` in other parts of the code. @@ -464,6 +510,12 @@ impl BackFillSync { /// Processes the batch with the given id. /// The batch must exist and be ready for processing + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn process_batch( &mut self, network: &mut SyncNetworkContext, @@ -503,8 +555,12 @@ impl BackFillSync { .beacon_processor() .send_chain_segment(process_id, blocks) { - crit!(self.log, "Failed to send backfill segment to processor."; "msg" => "process_batch", - "error" => %e, "batch" => self.processing_target); + crit!( + msg = "process_batch", + error = %e, + batch = ?self.processing_target, + "Failed to send backfill segment to processor." + ); // This is unlikely to happen but it would stall syncing since the batch now has no // blocks to continue, and the chain is expecting a processing result that won't // arrive. To mitigate this, (fake) fail this processing so that the batch is @@ -518,6 +574,12 @@ impl BackFillSync { /// The block processor has completed processing a batch. This function handles the result /// of the batch processor. /// If an error is returned the BackFill sync has failed. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn on_batch_process_result( &mut self, @@ -530,13 +592,15 @@ impl BackFillSync { // result let batch = match &self.current_processing_batch { Some(processing_id) if *processing_id != batch_id => { - debug!(self.log, "Unexpected batch result"; - "batch_epoch" => batch_id, "expected_batch_epoch" => processing_id); + debug!( + batch_epoch = %batch_id.as_u64(), + expected_batch_epoch = processing_id.as_u64(), + "Unexpected batch result" + ); return Ok(ProcessResult::Successful); } None => { - debug!(self.log, "Chain was not expecting a batch result"; - "batch_epoch" => batch_id); + debug!(%batch_id, "Chain was not expecting a batch result"); return Ok(ProcessResult::Successful); } _ => { @@ -566,8 +630,14 @@ impl BackFillSync { return Ok(ProcessResult::Successful); }; - debug!(self.log, "Backfill batch processed"; "result" => ?result, &batch, - "batch_epoch" => batch_id, "peer" => %peer, "client" => %network.client_type(peer)); + debug!( + ?result, + %batch, + batch_epoch = %batch_id, + %peer, + client = %network.client_type(peer), + "Backfill batch processed" + ); match result { BatchProcessResult::Success { @@ -591,7 +661,10 @@ impl BackFillSync { // check if the chain has completed syncing if self.check_completed() { // chain is completed - info!(self.log, "Backfill sync completed"; "blocks_processed" => self.validated_batches * T::EthSpec::slots_per_epoch()); + info!( + blocks_processed = self.validated_batches * T::EthSpec::slots_per_epoch(), + "Backfill sync completed" + ); self.set_state(BackFillState::Completed); Ok(ProcessResult::SyncCompleted) } else { @@ -619,10 +692,9 @@ impl BackFillSync { // repeatedly and are either malicious or faulty. We stop the backfill sync and // report all synced peers that have participated. warn!( - self.log, - "Backfill batch failed to download. Penalizing peers"; - "score_adjustment" => %penalty, - "batch_epoch"=> batch_id + score_adjustment = %penalty, + batch_epoch = %batch_id, + "Backfill batch failed to download. Penalizing peers" ); for peer in self.participating_peers.drain() { @@ -658,6 +730,12 @@ impl BackFillSync { } /// Processes the next ready batch. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn process_completed_batches( &mut self, network: &mut SyncNetworkContext, @@ -692,7 +770,10 @@ impl BackFillSync { BatchState::AwaitingValidation(_) => { // TODO: I don't think this state is possible, log a CRIT just in case. // If this is not observed, add it to the failed state branch above. - crit!(self.log, "Chain encountered a robust batch awaiting validation"; "batch" => self.processing_target); + crit!( + batch = ?self.processing_target, + "Chain encountered a robust batch awaiting validation" + ); self.processing_target -= BACKFILL_EPOCHS_PER_BATCH; if self.to_be_downloaded >= self.processing_target { @@ -718,6 +799,12 @@ impl BackFillSync { /// /// If a previous batch has been validated and it had been re-processed, penalize the original /// peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn advance_chain(&mut self, network: &mut SyncNetworkContext, validating_epoch: Epoch) { // make sure this epoch produces an advancement if validating_epoch >= self.current_start { @@ -745,9 +832,12 @@ impl BackFillSync { // A different peer sent the correct batch, the previous peer did not // We negatively score the original peer. let action = PeerAction::LowToleranceError; - debug!(self.log, "Re-processed batch validated. Scoring original peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = ?id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated. Scoring original peer" ); network.report_peer( attempt.peer_id, @@ -758,9 +848,12 @@ impl BackFillSync { // The same peer corrected it's previous mistake. There was an error, so we // negative score the original peer. let action = PeerAction::MidToleranceError; - debug!(self.log, "Re-processed batch validated by the same peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = ?id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated by the same peer" ); network.report_peer( attempt.peer_id, @@ -778,14 +871,11 @@ impl BackFillSync { } } BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => { - crit!( - self.log, - "batch indicates inconsistent chain state while advancing chain" - ) + crit!("batch indicates inconsistent chain state while advancing chain") } BatchState::AwaitingProcessing(..) => {} BatchState::Processing(_) => { - debug!(self.log, "Advancing chain while processing a batch"; "batch" => id, batch); + debug!(batch = %id, %batch, "Advancing chain while processing a batch"); if let Some(processing_id) = self.current_processing_batch { if id >= processing_id { self.current_processing_batch = None; @@ -803,7 +893,7 @@ impl BackFillSync { // won't have this batch, so we need to request it. self.to_be_downloaded -= BACKFILL_EPOCHS_PER_BATCH; } - debug!(self.log, "Backfill advanced"; "validated_epoch" => validating_epoch, "processing_target" => self.processing_target); + debug!(?validating_epoch, processing_target = ?self.processing_target, "Backfill advanced"); } /// An invalid batch has been received that could not be processed, but that can be retried. @@ -811,6 +901,12 @@ impl BackFillSync { /// These events occur when a peer has successfully responded with blocks, but the blocks we /// have received are incorrect or invalid. This indicates the peer has not performed as /// intended and can result in downvoting a peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn handle_invalid_batch( &mut self, network: &mut SyncNetworkContext, @@ -862,6 +958,12 @@ impl BackFillSync { } /// Sends and registers the request of a batch awaiting download. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn retry_batch_download( &mut self, network: &mut SyncNetworkContext, @@ -896,13 +998,19 @@ impl BackFillSync { self.send_batch(network, batch_id, peer) } else { // If we are here the chain has no more synced peers - info!(self.log, "Backfill sync paused"; "reason" => "insufficient_synced_peers"); + info!(reason = "insufficient_synced_peers", "Backfill sync paused"); self.set_state(BackFillState::Paused); Err(BackFillError::Paused) } } /// Requests the batch assigned to the given id from a given peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn send_batch( &mut self, network: &mut SyncNetworkContext, @@ -922,7 +1030,7 @@ impl BackFillSync { if let Err(e) = batch.start_downloading_from_peer(peer, request_id) { return self.fail_sync(BackFillError::BatchInvalidState(batch_id, e.0)); } - debug!(self.log, "Requesting batch"; "epoch" => batch_id, &batch); + debug!(epoch = %batch_id, %batch, "Requesting batch"); // register the batch for this peer self.active_requests @@ -933,8 +1041,7 @@ impl BackFillSync { } Err(e) => { // NOTE: under normal conditions this shouldn't happen but we handle it anyway - warn!(self.log, "Could not send batch request"; - "batch_id" => batch_id, "error" => ?e, &batch); + warn!(%batch_id, error = ?e, %batch,"Could not send batch request"); // register the failed download and check if the batch can be retried if let Err(e) = batch.start_downloading_from_peer(peer, 1) { return self.fail_sync(BackFillError::BatchInvalidState(batch_id, e.0)); @@ -963,6 +1070,12 @@ impl BackFillSync { /// When resuming a chain, this function searches for batches that need to be re-downloaded and /// transitions their state to redownload the batch. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn resume_batches(&mut self, network: &mut SyncNetworkContext) -> Result<(), BackFillError> { let batch_ids_to_retry = self .batches @@ -987,6 +1100,12 @@ impl BackFillSync { /// Attempts to request the next required batches from the peer pool if the chain is syncing. It will exhaust the peer /// pool and left over batches until the batch buffer is reached or all peers are exhausted. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn request_batches( &mut self, network: &mut SyncNetworkContext, @@ -1029,6 +1148,12 @@ impl BackFillSync { /// Creates the next required batch from the chain. If there are no more batches required, /// `false` is returned. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn include_next_batch(&mut self, network: &mut SyncNetworkContext) -> Option { // don't request batches beyond genesis; if self.last_batch_downloaded { @@ -1090,6 +1215,12 @@ impl BackFillSync { /// /// This errors if the beacon chain indicates that backfill sync has already completed or is /// not required. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn reset_start_epoch(&mut self) -> Result<(), ResetEpochError> { let anchor_info = self.beacon_chain.store.get_anchor_info(); if anchor_info.block_backfill_complete(self.beacon_chain.genesis_backfill_slot) { @@ -1103,6 +1234,12 @@ impl BackFillSync { } /// Checks with the beacon chain if backfill sync has completed. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn check_completed(&mut self) -> bool { if self.would_complete(self.current_start) { // Check that the beacon chain agrees @@ -1111,13 +1248,19 @@ impl BackFillSync { if anchor_info.block_backfill_complete(self.beacon_chain.genesis_backfill_slot) { return true; } else { - error!(self.log, "Backfill out of sync with beacon chain"); + error!("Backfill out of sync with beacon chain"); } } false } /// Checks if backfill would complete by syncing to `start_epoch`. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn would_complete(&self, start_epoch: Epoch) -> bool { start_epoch <= self @@ -1127,10 +1270,22 @@ impl BackFillSync { } /// Updates the global network state indicating the current state of a backfill sync. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn set_state(&self, state: BackFillState) { *self.network_globals.backfill_state.write() = state; } + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn state(&self) -> BackFillState { self.network_globals.backfill_state.read().clone() } diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index a29f9cf402..8c884f644e 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -41,11 +41,11 @@ use lighthouse_network::service::api_types::SingleLookupReqId; use lighthouse_network::{PeerAction, PeerId}; use lru_cache::LRUTimeCache; pub use single_block_lookup::{BlobRequestState, BlockRequestState, CustodyRequestState}; -use slog::{debug, error, warn, Logger}; use std::collections::hash_map::Entry; use std::sync::Arc; use std::time::Duration; use store::Hash256; +use tracing::{debug, error, instrument, warn}; use types::{BlobSidecar, DataColumnSidecar, EthSpec, SignedBeaconBlock}; pub mod common; @@ -116,9 +116,6 @@ pub struct BlockLookups { // TODO: Why not index lookups by block_root? single_block_lookups: FnvHashMap>, - - /// The logger for the import manager. - log: Logger, } #[cfg(test)] @@ -130,27 +127,45 @@ use lighthouse_network::service::api_types::Id; pub(crate) type BlockLookupSummary = (Id, Hash256, Option, Vec); impl BlockLookups { - pub fn new(log: Logger) -> Self { + #[instrument(parent = None,level = "info", fields(service = "lookup_sync"), name = "lookup_sync")] + pub fn new() -> Self { Self { failed_chains: LRUTimeCache::new(Duration::from_secs( FAILED_CHAINS_CACHE_EXPIRY_SECONDS, )), single_block_lookups: Default::default(), - log, } } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn insert_failed_chain(&mut self, block_root: Hash256) { self.failed_chains.insert(block_root); } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn get_failed_chains(&mut self) -> Vec { self.failed_chains.keys().cloned().collect() } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn active_single_lookups(&self) -> Vec { self.single_block_lookups .iter() @@ -159,6 +174,12 @@ impl BlockLookups { } /// Returns a vec of all parent lookup chains by tip, in descending slot order (tip first) + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn active_parent_lookups(&self) -> Vec { compute_parent_chains( &self @@ -173,6 +194,12 @@ impl BlockLookups { /// Creates a parent lookup for the block with the given `block_root` and immediately triggers it. /// If a parent lookup exists or is triggered, a current lookup will be created. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_child_and_parent( &mut self, block_root: Hash256, @@ -202,6 +229,12 @@ impl BlockLookups { /// Seach a block whose parent root is unknown. /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_unknown_block( &mut self, block_root: Hash256, @@ -217,6 +250,12 @@ impl BlockLookups { /// - `block_root_to_search` is a failed chain /// /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_parent_of_child( &mut self, block_root_to_search: Hash256, @@ -238,7 +277,7 @@ impl BlockLookups { if (block_would_extend_chain || trigger_is_chain_tip) && parent_chain.len() >= PARENT_DEPTH_TOLERANCE { - debug!(self.log, "Parent lookup chain too long"; "block_root" => ?block_root_to_search); + debug!(block_root = ?block_root_to_search, "Parent lookup chain too long"); // Searching for this parent would extend a parent chain over the max // Insert the tip only to failed chains @@ -283,9 +322,10 @@ impl BlockLookups { }); } else { // Should never happen, log error and continue the lookup drop - error!(self.log, "Unable to transition lookup to range sync"; - "error" => "Parent chain tip lookup not found", - "block_root" => ?parent_chain_tip + error!( + error = "Parent chain tip lookup not found", + block_root = ?parent_chain_tip, + "Unable to transition lookup to range sync" ); } @@ -299,9 +339,10 @@ impl BlockLookups { self.drop_lookup_and_children(*lookup_id); } else { // Should never happen - error!(self.log, "Unable to transition lookup to range sync"; - "error" => "Block to drop lookup not found", - "block_root" => ?block_to_drop + error!( + error = "Block to drop lookup not found", + block_root = ?block_to_drop, + "Unable to transition lookup to range sync" ); } @@ -316,6 +357,12 @@ impl BlockLookups { /// Searches for a single block hash. If the blocks parent is unknown, a chain of blocks is /// constructed. /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn new_current_lookup( &mut self, block_root: Hash256, @@ -326,7 +373,7 @@ impl BlockLookups { ) -> bool { // If this block or it's parent is part of a known failed chain, ignore it. if self.failed_chains.contains(&block_root) { - debug!(self.log, "Block is from a past failed chain. Dropping"; "block_root" => ?block_root); + debug!(?block_root, "Block is from a past failed chain. Dropping"); for peer_id in peers { cx.report_peer(*peer_id, PeerAction::MidToleranceError, "failed_chain"); } @@ -343,12 +390,15 @@ impl BlockLookups { let component_type = block_component.get_type(); let imported = lookup.add_child_components(block_component); if !imported { - debug!(self.log, "Lookup child component ignored"; "block_root" => ?block_root, "type" => component_type); + debug!( + ?block_root, + component_type, "Lookup child component ignored" + ); } } if let Err(e) = self.add_peers_to_lookup_and_ancestors(lookup_id, peers, cx) { - warn!(self.log, "Error adding peers to ancestor lookup"; "error" => ?e); + warn!(error = ?e, "Error adding peers to ancestor lookup"); } return true; @@ -361,7 +411,7 @@ impl BlockLookups { .iter() .any(|(_, lookup)| lookup.is_for_block(awaiting_parent)) { - warn!(self.log, "Ignoring child lookup parent lookup not found"; "block_root" => ?awaiting_parent); + warn!(block_root = ?awaiting_parent, "Ignoring child lookup parent lookup not found"); return false; } } @@ -369,7 +419,7 @@ impl BlockLookups { // Lookups contain untrusted data, bound the total count of lookups hold in memory to reduce // the risk of OOM in case of bugs of malicious activity. if self.single_block_lookups.len() > MAX_LOOKUPS { - warn!(self.log, "Dropping lookup reached max"; "block_root" => ?block_root); + warn!(?block_root, "Dropping lookup reached max"); return false; } @@ -387,18 +437,19 @@ impl BlockLookups { Entry::Vacant(entry) => entry.insert(lookup), Entry::Occupied(_) => { // Should never happen - warn!(self.log, "Lookup exists with same id"; "id" => id); + warn!(id, "Lookup exists with same id"); return false; } }; debug!( - self.log, - "Created block lookup"; - "peer_ids" => ?peers, - "block_root" => ?block_root, - "awaiting_parent" => awaiting_parent.map(|root| root.to_string()).unwrap_or("none".to_owned()), - "id" => lookup.id, + ?peers, + ?block_root, + awaiting_parent = awaiting_parent + .map(|root| root.to_string()) + .unwrap_or("none".to_owned()), + id = lookup.id, + "Created block lookup" ); metrics::inc_counter(&metrics::SYNC_LOOKUP_CREATED); @@ -414,6 +465,12 @@ impl BlockLookups { /* Lookup responses */ /// Process a block or blob response received from a single lookup request. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_download_response>( &mut self, id: SingleLookupReqId, @@ -437,7 +494,7 @@ impl BlockLookups { let Some(lookup) = self.single_block_lookups.get_mut(&id.lookup_id) else { // We don't have the ability to cancel in-flight RPC requests. So this can happen // if we started this RPC request, and later saw the block/blobs via gossip. - debug!(self.log, "Block returned for single block lookup not present"; "id" => ?id); + debug!(?id, "Block returned for single block lookup not present"); return Err(LookupRequestError::UnknownLookup); }; @@ -448,12 +505,12 @@ impl BlockLookups { match response { Ok((response, peer_group, seen_timestamp)) => { - debug!(self.log, - "Received lookup download success"; - "block_root" => ?block_root, - "id" => ?id, - "peer_group" => ?peer_group, - "response_type" => ?response_type, + debug!( + ?block_root, + ?id, + ?peer_group, + ?response_type, + "Received lookup download success" ); // Here we could check if response extends a parent chain beyond its max length. @@ -481,12 +538,12 @@ impl BlockLookups { Err(e) => { // No need to log peer source here. When sending a DataColumnsByRoot request we log // the peer and the request ID which is linked to this `id` value here. - debug!(self.log, - "Received lookup download failure"; - "block_root" => ?block_root, - "id" => ?id, - "response_type" => ?response_type, - "error" => ?e, + debug!( + ?block_root, + ?id, + ?response_type, + error = ?e, + "Received lookup download failure" ); request_state.on_download_failure(id.req_id)?; @@ -499,6 +556,12 @@ impl BlockLookups { /* Error responses */ + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn peer_disconnected(&mut self, peer_id: &PeerId) { for (_, lookup) in self.single_block_lookups.iter_mut() { lookup.remove_peer(peer_id); @@ -507,6 +570,12 @@ impl BlockLookups { /* Processing responses */ + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_processing_result( &mut self, process_type: BlockProcessType, @@ -527,6 +596,12 @@ impl BlockLookups { self.on_lookup_result(process_type.id(), lookup_result, "processing_result", cx); } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_processing_result_inner>( &mut self, lookup_id: SingleLookupId, @@ -534,7 +609,7 @@ impl BlockLookups { cx: &mut SyncNetworkContext, ) -> Result { let Some(lookup) = self.single_block_lookups.get_mut(&lookup_id) else { - debug!(self.log, "Unknown single block lookup"; "id" => lookup_id); + debug!(id = lookup_id, "Unknown single block lookup"); return Err(LookupRequestError::UnknownLookup); }; @@ -544,12 +619,11 @@ impl BlockLookups { .get_state_mut(); debug!( - self.log, - "Received lookup processing result"; - "component" => ?R::response_type(), - "block_root" => ?block_root, - "id" => lookup_id, - "result" => ?result, + component = ?R::response_type(), + ?block_root, + id = lookup_id, + ?result, + "Received lookup processing result" ); let action = match result { @@ -581,20 +655,15 @@ impl BlockLookups { BlockProcessingResult::Err(BlockError::DuplicateImportStatusUnknown(..)) => { // This is unreachable because RPC blocks do not undergo gossip verification, and // this error can *only* come from gossip verification. - error!( - self.log, - "Single block lookup hit unreachable condition"; - "block_root" => ?block_root - ); + error!(?block_root, "Single block lookup hit unreachable condition"); Action::Drop } BlockProcessingResult::Ignored => { // Beacon processor signalled to ignore the block processing result. // This implies that the cpu is overloaded. Drop the request. warn!( - self.log, - "Lookup component processing ignored, cpu might be overloaded"; - "component" => ?R::response_type(), + component = ?R::response_type(), + "Lookup component processing ignored, cpu might be overloaded" ); Action::Drop } @@ -602,7 +671,7 @@ impl BlockLookups { match e { BlockError::BeaconChainError(e) => { // Internal error - error!(self.log, "Beacon chain error processing lookup component"; "block_root" => %block_root, "error" => ?e); + error!(%block_root, error = ?e, "Beacon chain error processing lookup component"); Action::Drop } BlockError::ParentUnknown { parent_root, .. } => { @@ -618,10 +687,9 @@ impl BlockLookups { // These errors indicate that the execution layer is offline // and failed to validate the execution payload. Do not downscore peer. debug!( - self.log, - "Single block lookup failed. Execution layer is offline / unsynced / misconfigured"; - "block_root" => ?block_root, - "error" => ?e + ?block_root, + error = ?e, + "Single block lookup failed. Execution layer is offline / unsynced / misconfigured" ); Action::Drop } @@ -629,7 +697,7 @@ impl BlockLookups { if e.category() == AvailabilityCheckErrorCategory::Internal => { // There errors indicate internal problems and should not downscore the peer - warn!(self.log, "Internal availability check failure"; "block_root" => ?block_root, "error" => ?e); + warn!(?block_root, error = ?e, "Internal availability check failure"); // Here we choose *not* to call `on_processing_failure` because this could result in a bad // lookup state transition. This error invalidates both blob and block requests, and we don't know the @@ -638,7 +706,12 @@ impl BlockLookups { Action::Drop } other => { - debug!(self.log, "Invalid lookup component"; "block_root" => ?block_root, "component" => ?R::response_type(), "error" => ?other); + debug!( + ?block_root, + component = ?R::response_type(), + error = ?other, + "Invalid lookup component" + ); let peer_group = request_state.on_processing_failure()?; let peers_to_penalize: Vec<_> = match other { // Note: currenlty only InvalidColumn errors have index granularity, @@ -685,7 +758,12 @@ impl BlockLookups { Action::ParentUnknown { parent_root } => { let peers = lookup.all_peers(); lookup.set_awaiting_parent(parent_root); - debug!(self.log, "Marking lookup as awaiting parent"; "id" => lookup.id, "block_root" => ?block_root, "parent_root" => ?parent_root); + debug!( + id = lookup.id, + ?block_root, + ?parent_root, + "Marking lookup as awaiting parent" + ); self.search_parent_of_child(parent_root, block_root, &peers, cx); Ok(LookupResult::Pending) } @@ -700,6 +778,12 @@ impl BlockLookups { } } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_external_processing_result( &mut self, block_root: Hash256, @@ -725,13 +809,24 @@ impl BlockLookups { } /// Makes progress on the immediate children of `block_root` + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn continue_child_lookups(&mut self, block_root: Hash256, cx: &mut SyncNetworkContext) { let mut lookup_results = vec![]; // < need to buffer lookup results to not re-borrow &mut self for (id, lookup) in self.single_block_lookups.iter_mut() { if lookup.awaiting_parent() == Some(block_root) { lookup.resolve_awaiting_parent(); - debug!(self.log, "Continuing child lookup"; "parent_root" => ?block_root, "id" => id, "block_root" => ?lookup.block_root()); + debug!( + parent_root = ?block_root, + id, + block_root = ?lookup.block_root(), + "Continuing child lookup" + ); let result = lookup.continue_requests(cx); lookup_results.push((*id, result)); } @@ -745,12 +840,19 @@ impl BlockLookups { /// Drops `dropped_id` lookup and all its children recursively. Lookups awaiting a parent need /// the parent to make progress to resolve, therefore we must drop them if the parent is /// dropped. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn drop_lookup_and_children(&mut self, dropped_id: SingleLookupId) { if let Some(dropped_lookup) = self.single_block_lookups.remove(&dropped_id) { - debug!(self.log, "Dropping lookup"; - "id" => ?dropped_id, - "block_root" => ?dropped_lookup.block_root(), - "awaiting_parent" => ?dropped_lookup.awaiting_parent(), + debug!( + id = ?dropped_id, + block_root = ?dropped_lookup.block_root(), + awaiting_parent = ?dropped_lookup.awaiting_parent(), + "Dropping lookup" ); let child_lookups = self @@ -768,6 +870,12 @@ impl BlockLookups { /// Common handler a lookup request error, drop it and update metrics /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn on_lookup_result( &mut self, id: SingleLookupId, @@ -779,13 +887,13 @@ impl BlockLookups { Ok(LookupResult::Pending) => true, // no action Ok(LookupResult::Completed) => { if let Some(lookup) = self.single_block_lookups.remove(&id) { - debug!(self.log, "Dropping completed lookup"; "block" => ?lookup.block_root(), "id" => id); + debug!(block = ?lookup.block_root(), id, "Dropping completed lookup"); metrics::inc_counter(&metrics::SYNC_LOOKUP_COMPLETED); // Block imported, continue the requests of pending child blocks self.continue_child_lookups(lookup.block_root(), cx); self.update_metrics(); } else { - debug!(self.log, "Attempting to drop non-existent lookup"; "id" => id); + debug!(id, "Attempting to drop non-existent lookup"); } false } @@ -793,7 +901,7 @@ impl BlockLookups { // update metrics because the lookup does not exist. Err(LookupRequestError::UnknownLookup) => false, Err(error) => { - debug!(self.log, "Dropping lookup on request error"; "id" => id, "source" => source, "error" => ?error); + debug!(id, source, ?error, "Dropping lookup on request error"); metrics::inc_counter_vec(&metrics::SYNC_LOOKUP_DROPPED, &[error.into()]); self.drop_lookup_and_children(id); self.update_metrics(); @@ -805,12 +913,24 @@ impl BlockLookups { /* Helper functions */ /// Drops all the single block requests and returns how many requests were dropped. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn drop_single_block_requests(&mut self) -> usize { let requests_to_drop = self.single_block_lookups.len(); self.single_block_lookups.clear(); requests_to_drop } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn update_metrics(&self) { metrics::set_gauge( &metrics::SYNC_SINGLE_BLOCK_LOOKUPS, @@ -819,6 +939,12 @@ impl BlockLookups { } /// Perform some prune operations on lookups on some interval + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn prune_lookups(&mut self) { self.drop_lookups_without_peers(); self.drop_stuck_lookups(); @@ -842,6 +968,12 @@ impl BlockLookups { /// /// Instead there's no negative for keeping lookups with no peers around for some time. If we /// regularly prune them, it should not be a memory concern (TODO: maybe yes!). + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn drop_lookups_without_peers(&mut self) { for (lookup_id, block_root) in self .single_block_lookups @@ -857,9 +989,10 @@ impl BlockLookups { .map(|lookup| (lookup.id, lookup.block_root())) .collect::>() { - debug!(self.log, "Dropping lookup with no peers"; - "id" => lookup_id, - "block_root" => ?block_root + debug!( + id = lookup_id, + %block_root, + "Dropping lookup with no peers" ); self.drop_lookup_and_children(lookup_id); } @@ -878,6 +1011,12 @@ impl BlockLookups { /// /// - One single clear warn level log per stuck incident /// - If the original bug is sporadic, it reduces the time a node is stuck from forever to 15 min + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn drop_stuck_lookups(&mut self) { // While loop to find and drop all disjoint trees of potentially stuck lookups. while let Some(stuck_lookup) = self.single_block_lookups.values().find(|lookup| { @@ -886,7 +1025,7 @@ impl BlockLookups { let ancestor_stuck_lookup = match self.find_oldest_ancestor_lookup(stuck_lookup) { Ok(lookup) => lookup, Err(e) => { - warn!(self.log, "Error finding oldest ancestor lookup"; "error" => ?e); + warn!(error = ?e,"Error finding oldest ancestor lookup"); // Default to dropping the lookup that exceeds the max duration so at least // eventually sync should be unstuck stuck_lookup @@ -894,16 +1033,18 @@ impl BlockLookups { }; if stuck_lookup.id == ancestor_stuck_lookup.id { - warn!(self.log, "Notify the devs a sync lookup is stuck"; - "block_root" => ?stuck_lookup.block_root(), - "lookup" => ?stuck_lookup, + warn!( + block_root = ?stuck_lookup.block_root(), + lookup = ?stuck_lookup, + "Notify the devs a sync lookup is stuck" ); } else { - warn!(self.log, "Notify the devs a sync lookup is stuck"; - "block_root" => ?stuck_lookup.block_root(), - "lookup" => ?stuck_lookup, - "ancestor_block_root" => ?ancestor_stuck_lookup.block_root(), - "ancestor_lookup" => ?ancestor_stuck_lookup, + warn!( + block_root = ?stuck_lookup.block_root(), + lookup = ?stuck_lookup, + ancestor_block_root = ?ancestor_stuck_lookup.block_root(), + ancestor_lookup = ?ancestor_stuck_lookup, + "Notify the devs a sync lookup is stuck" ); } @@ -913,6 +1054,12 @@ impl BlockLookups { } /// Recursively find the oldest ancestor lookup of another lookup + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn find_oldest_ancestor_lookup<'a>( &'a self, lookup: &'a SingleBlockLookup, @@ -937,6 +1084,12 @@ impl BlockLookups { /// Adds peers to a lookup and its ancestors recursively. /// Note: Takes a `lookup_id` as argument to allow recursion on mutable lookups, without having /// to duplicate the code to add peers to a lookup + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn add_peers_to_lookup_and_ancestors( &mut self, lookup_id: SingleLookupId, @@ -952,9 +1105,10 @@ impl BlockLookups { for peer in peers { if lookup.add_peer(*peer) { added_some_peer = true; - debug!(self.log, "Adding peer to existing single block lookup"; - "block_root" => ?lookup.block_root(), - "peer" => ?peer + debug!( + block_root = ?lookup.block_root(), + ?peer, + "Adding peer to existing single block lookup" ); } } diff --git a/beacon_node/network/src/sync/block_sidecar_coupling.rs b/beacon_node/network/src/sync/block_sidecar_coupling.rs index 6c8a8eab63..ef9285c8dc 100644 --- a/beacon_node/network/src/sync/block_sidecar_coupling.rs +++ b/beacon_node/network/src/sync/block_sidecar_coupling.rs @@ -1,89 +1,156 @@ use beacon_chain::{ block_verification_types::RpcBlock, data_column_verification::CustodyDataColumn, get_block_root, }; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, +use lighthouse_network::service::api_types::{ + BlobsByRangeRequestId, BlocksByRangeRequestId, DataColumnsByRangeRequestId, }; +use std::{collections::HashMap, sync::Arc}; use types::{ - BlobSidecar, ChainSpec, ColumnIndex, DataColumnSidecar, EthSpec, Hash256, RuntimeVariableList, - SignedBeaconBlock, + BlobSidecar, ChainSpec, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, + Hash256, RuntimeVariableList, SignedBeaconBlock, }; -#[derive(Debug)] pub struct RangeBlockComponentsRequest { /// Blocks we have received awaiting for their corresponding sidecar. - blocks: VecDeque>>, + blocks_request: ByRangeRequest>>>, /// Sidecars we have received awaiting for their corresponding block. - blobs: VecDeque>>, - data_columns: VecDeque>>, - /// Whether the individual RPC request for blocks is finished or not. - is_blocks_stream_terminated: bool, - /// Whether the individual RPC request for sidecars is finished or not. - is_sidecars_stream_terminated: bool, - custody_columns_streams_terminated: usize, - /// Used to determine if this accumulator should wait for a sidecars stream termination - expects_blobs: bool, - expects_custody_columns: Option>, - /// Used to determine if the number of data columns stream termination this accumulator should - /// wait for. This may be less than the number of `expects_custody_columns` due to request batching. - num_custody_column_requests: Option, + block_data_request: RangeBlockDataRequest, +} + +enum ByRangeRequest { + Active(I), + Complete(T), +} + +enum RangeBlockDataRequest { + NoData, + Blobs(ByRangeRequest>>>), + DataColumns { + requests: HashMap< + DataColumnsByRangeRequestId, + ByRangeRequest>, + >, + expected_custody_columns: Vec, + }, } impl RangeBlockComponentsRequest { pub fn new( - expects_blobs: bool, - expects_custody_columns: Option>, - num_custody_column_requests: Option, + blocks_req_id: BlocksByRangeRequestId, + blobs_req_id: Option, + data_columns: Option<(Vec, Vec)>, ) -> Self { - Self { - blocks: <_>::default(), - blobs: <_>::default(), - data_columns: <_>::default(), - is_blocks_stream_terminated: false, - is_sidecars_stream_terminated: false, - custody_columns_streams_terminated: 0, - expects_blobs, - expects_custody_columns, - num_custody_column_requests, - } - } - - pub fn add_blocks(&mut self, blocks: Vec>>) { - for block in blocks { - self.blocks.push_back(block); - } - self.is_blocks_stream_terminated = true; - } - - pub fn add_blobs(&mut self, blobs: Vec>>) { - for blob in blobs { - self.blobs.push_back(blob); - } - self.is_sidecars_stream_terminated = true; - } - - pub fn add_custody_columns(&mut self, columns: Vec>>) { - for column in columns { - self.data_columns.push_back(column); - } - // TODO(das): this mechanism is dangerous, if somehow there are two requests for the - // same column index it can terminate early. This struct should track that all requests - // for all custody columns terminate. - self.custody_columns_streams_terminated += 1; - } - - pub fn into_responses(self, spec: &ChainSpec) -> Result>, String> { - if let Some(expects_custody_columns) = self.expects_custody_columns.clone() { - self.into_responses_with_custody_columns(expects_custody_columns, spec) + let block_data_request = if let Some(blobs_req_id) = blobs_req_id { + RangeBlockDataRequest::Blobs(ByRangeRequest::Active(blobs_req_id)) + } else if let Some((requests, expected_custody_columns)) = data_columns { + RangeBlockDataRequest::DataColumns { + requests: requests + .into_iter() + .map(|id| (id, ByRangeRequest::Active(id))) + .collect(), + expected_custody_columns, + } } else { - self.into_responses_with_blobs(spec) + RangeBlockDataRequest::NoData + }; + + Self { + blocks_request: ByRangeRequest::Active(blocks_req_id), + block_data_request, } } - fn into_responses_with_blobs(self, spec: &ChainSpec) -> Result>, String> { - let RangeBlockComponentsRequest { blocks, blobs, .. } = self; + pub fn add_blocks( + &mut self, + req_id: BlocksByRangeRequestId, + blocks: Vec>>, + ) -> Result<(), String> { + self.blocks_request.finish(req_id, blocks) + } + pub fn add_blobs( + &mut self, + req_id: BlobsByRangeRequestId, + blobs: Vec>>, + ) -> Result<(), String> { + match &mut self.block_data_request { + RangeBlockDataRequest::NoData => Err("received blobs but expected no data".to_owned()), + RangeBlockDataRequest::Blobs(ref mut req) => req.finish(req_id, blobs), + RangeBlockDataRequest::DataColumns { .. } => { + Err("received blobs but expected data columns".to_owned()) + } + } + } + + pub fn add_custody_columns( + &mut self, + req_id: DataColumnsByRangeRequestId, + columns: Vec>>, + ) -> Result<(), String> { + match &mut self.block_data_request { + RangeBlockDataRequest::NoData => { + Err("received data columns but expected no data".to_owned()) + } + RangeBlockDataRequest::Blobs(_) => { + Err("received data columns but expected blobs".to_owned()) + } + RangeBlockDataRequest::DataColumns { + ref mut requests, .. + } => { + let req = requests + .get_mut(&req_id) + .ok_or(format!("unknown data columns by range req_id {req_id}"))?; + req.finish(req_id, columns) + } + } + } + + pub fn responses(&self, spec: &ChainSpec) -> Option>, String>> { + let Some(blocks) = self.blocks_request.to_finished() else { + return None; + }; + + match &self.block_data_request { + RangeBlockDataRequest::NoData => { + Some(Self::responses_with_blobs(blocks.to_vec(), vec![], spec)) + } + RangeBlockDataRequest::Blobs(request) => { + let Some(blobs) = request.to_finished() else { + return None; + }; + Some(Self::responses_with_blobs( + blocks.to_vec(), + blobs.to_vec(), + spec, + )) + } + RangeBlockDataRequest::DataColumns { + requests, + expected_custody_columns, + } => { + let mut data_columns = vec![]; + for req in requests.values() { + let Some(data) = req.to_finished() else { + return None; + }; + data_columns.extend(data.clone()) + } + + Some(Self::responses_with_custody_columns( + blocks.to_vec(), + data_columns, + expected_custody_columns, + spec, + )) + } + } + } + + fn responses_with_blobs( + blocks: Vec>>, + blobs: Vec>>, + spec: &ChainSpec, + ) -> Result>, String> { // There can't be more more blobs than blocks. i.e. sending any blob (empty // included) for a skipped slot is not permitted. let mut responses = Vec::with_capacity(blocks.len()); @@ -129,17 +196,12 @@ impl RangeBlockComponentsRequest { Ok(responses) } - fn into_responses_with_custody_columns( - self, - expects_custody_columns: Vec, + fn responses_with_custody_columns( + blocks: Vec>>, + data_columns: DataColumnSidecarList, + expects_custody_columns: &[ColumnIndex], spec: &ChainSpec, ) -> Result>, String> { - let RangeBlockComponentsRequest { - blocks, - data_columns, - .. - } = self; - // Group data columns by block_root and index let mut data_columns_by_block = HashMap::>>>::new(); @@ -177,7 +239,7 @@ impl RangeBlockComponentsRequest { }; let mut custody_columns = vec![]; - for index in &expects_custody_columns { + for index in expects_custody_columns { let Some(data_column) = data_columns_by_index.remove(index) else { return Err(format!("No column for block {block_root:?} index {index}")); }; @@ -195,8 +257,14 @@ impl RangeBlockComponentsRequest { )); } - RpcBlock::new_with_custody_columns(Some(block_root), block, custody_columns, spec) - .map_err(|e| format!("{e:?}"))? + RpcBlock::new_with_custody_columns( + Some(block_root), + block, + custody_columns, + expects_custody_columns.len(), + spec, + ) + .map_err(|e| format!("{e:?}"))? } else { RpcBlock::new_without_blobs(Some(block_root), block) }); @@ -210,20 +278,27 @@ impl RangeBlockComponentsRequest { Ok(rpc_blocks) } +} - pub fn is_finished(&self) -> bool { - if !self.is_blocks_stream_terminated { - return false; - } - if self.expects_blobs && !self.is_sidecars_stream_terminated { - return false; - } - if let Some(expects_custody_column_responses) = self.num_custody_column_requests { - if self.custody_columns_streams_terminated < expects_custody_column_responses { - return false; +impl ByRangeRequest { + fn finish(&mut self, id: I, data: T) -> Result<(), String> { + match self { + Self::Active(expected_id) => { + if expected_id != &id { + return Err(format!("unexpected req_id expected {expected_id} got {id}")); + } + *self = Self::Complete(data); + Ok(()) } + Self::Complete(_) => Err("request already complete".to_owned()), + } + } + + fn to_finished(&self) -> Option<&T> { + match self { + Self::Active(_) => None, + Self::Complete(data) => Some(data), } - true } } @@ -233,9 +308,52 @@ mod tests { use beacon_chain::test_utils::{ generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, NumBlobs, }; + use lighthouse_network::service::api_types::{ + BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, + DataColumnsByRangeRequestId, Id, RangeRequestId, + }; use rand::SeedableRng; use std::sync::Arc; - use types::{test_utils::XorShiftRng, ForkName, MinimalEthSpec as E, SignedBeaconBlock}; + use types::{test_utils::XorShiftRng, Epoch, ForkName, MinimalEthSpec as E, SignedBeaconBlock}; + + fn components_id() -> ComponentsByRangeRequestId { + ComponentsByRangeRequestId { + id: 0, + requester: RangeRequestId::RangeSync { + chain_id: 1, + batch_id: Epoch::new(0), + }, + } + } + + fn blocks_id(parent_request_id: ComponentsByRangeRequestId) -> BlocksByRangeRequestId { + BlocksByRangeRequestId { + id: 1, + parent_request_id, + } + } + + fn blobs_id(parent_request_id: ComponentsByRangeRequestId) -> BlobsByRangeRequestId { + BlobsByRangeRequestId { + id: 1, + parent_request_id, + } + } + + fn columns_id( + id: Id, + parent_request_id: ComponentsByRangeRequestId, + ) -> DataColumnsByRangeRequestId { + DataColumnsByRangeRequestId { + id, + parent_request_id, + } + } + + fn is_finished(info: &RangeBlockComponentsRequest) -> bool { + let spec = test_spec::(); + info.responses(&spec).is_some() + } #[test] fn no_blobs_into_responses() { @@ -248,14 +366,15 @@ mod tests { .into() }) .collect::>>>(); - let mut info = RangeBlockComponentsRequest::::new(false, None, None); + + let blocks_req_id = blocks_id(components_id()); + let mut info = RangeBlockComponentsRequest::::new(blocks_req_id, None, None); // Send blocks and complete terminate response - info.add_blocks(blocks); + info.add_blocks(blocks_req_id, blocks).unwrap(); // Assert response is finished and RpcBlocks can be constructed - assert!(info.is_finished()); - info.into_responses(&test_spec::()).unwrap(); + info.responses(&test_spec::()).unwrap().unwrap(); } #[test] @@ -275,18 +394,22 @@ mod tests { .into() }) .collect::>>>(); - let mut info = RangeBlockComponentsRequest::::new(true, None, None); + + let components_id = components_id(); + let blocks_req_id = blocks_id(components_id); + let blobs_req_id = blobs_id(components_id); + let mut info = + RangeBlockComponentsRequest::::new(blocks_req_id, Some(blobs_req_id), None); // Send blocks and complete terminate response - info.add_blocks(blocks); + info.add_blocks(blocks_req_id, blocks).unwrap(); // Expect no blobs returned - info.add_blobs(vec![]); + info.add_blobs(blobs_req_id, vec![]).unwrap(); // Assert response is finished and RpcBlocks can be constructed, even if blobs weren't returned. // This makes sure we don't expect blobs here when they have expired. Checking this logic should // be hendled elsewhere. - assert!(info.is_finished()); - info.into_responses(&test_spec::()).unwrap(); + info.responses(&test_spec::()).unwrap().unwrap(); } #[test] @@ -304,40 +427,49 @@ mod tests { ) }) .collect::>(); + + let components_id = components_id(); + let blocks_req_id = blocks_id(components_id); + let columns_req_id = expects_custody_columns + .iter() + .enumerate() + .map(|(i, _)| columns_id(i as Id, components_id)) + .collect::>(); let mut info = RangeBlockComponentsRequest::::new( - false, - Some(expects_custody_columns.clone()), - Some(expects_custody_columns.len()), + blocks_req_id, + None, + Some((columns_req_id.clone(), expects_custody_columns.clone())), ); // Send blocks and complete terminate response - info.add_blocks(blocks.iter().map(|b| b.0.clone().into()).collect()); + info.add_blocks( + blocks_req_id, + blocks.iter().map(|b| b.0.clone().into()).collect(), + ) + .unwrap(); // Assert response is not finished - assert!(!info.is_finished()); + assert!(!is_finished(&info)); // Send data columns for (i, &column_index) in expects_custody_columns.iter().enumerate() { info.add_custody_columns( + columns_req_id.get(i).copied().unwrap(), blocks .iter() .flat_map(|b| b.1.iter().filter(|d| d.index == column_index).cloned()) .collect(), - ); + ) + .unwrap(); if i < expects_custody_columns.len() - 1 { assert!( - !info.is_finished(), + !is_finished(&info), "requested should not be finished at loop {i}" ); - } else { - assert!( - info.is_finished(), - "request should be finishied at loop {i}" - ); } } // All completed construct response - info.into_responses(&spec).unwrap(); + info.responses(&spec).unwrap().unwrap(); } #[test] @@ -353,10 +485,18 @@ mod tests { (0..batched_column_requests.len() as u32).collect::>(); let num_of_data_column_requests = custody_column_request_ids.len(); + let components_id = components_id(); + let blocks_req_id = blocks_id(components_id); + let columns_req_id = batched_column_requests + .iter() + .enumerate() + .map(|(i, _)| columns_id(i as Id, components_id)) + .collect::>(); + let mut info = RangeBlockComponentsRequest::::new( - false, - Some(expects_custody_columns.clone()), - Some(num_of_data_column_requests), + blocks_req_id, + None, + Some((columns_req_id.clone(), expects_custody_columns.clone())), ); let mut rng = XorShiftRng::from_seed([42; 16]); @@ -372,13 +512,18 @@ mod tests { .collect::>(); // Send blocks and complete terminate response - info.add_blocks(blocks.iter().map(|b| b.0.clone().into()).collect()); + info.add_blocks( + blocks_req_id, + blocks.iter().map(|b| b.0.clone().into()).collect(), + ) + .unwrap(); // Assert response is not finished - assert!(!info.is_finished()); + assert!(!is_finished(&info)); for (i, column_indices) in batched_column_requests.iter().enumerate() { // Send the set of columns in the same batch request info.add_custody_columns( + columns_req_id.get(i).copied().unwrap(), blocks .iter() .flat_map(|b| { @@ -387,19 +532,18 @@ mod tests { .cloned() }) .collect::>(), - ); + ) + .unwrap(); if i < num_of_data_column_requests - 1 { assert!( - !info.is_finished(), + !is_finished(&info), "requested should not be finished at loop {i}" ); - } else { - assert!(info.is_finished(), "request should be finished at loop {i}"); } } // All completed construct response - info.into_responses(&spec).unwrap(); + info.responses(&spec).unwrap().unwrap(); } } diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index a9e5f646cc..9a48e9aa5d 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -63,12 +63,13 @@ use lighthouse_network::service::api_types::{ use lighthouse_network::types::{NetworkGlobals, SyncState}; use lighthouse_network::SyncInfo; use lighthouse_network::{PeerAction, PeerId}; +use logging::crit; use lru_cache::LRUTimeCache; -use slog::{crit, debug, error, info, o, trace, warn, Logger}; use std::ops::Sub; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{ BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, Slot, }; @@ -246,9 +247,6 @@ pub struct SyncManager { notified_unknown_roots: LRUTimeCache<(PeerId, Hash256)>, sampling: Sampling, - - /// The logger for the import manager. - log: Logger, } /// Spawns a new `SyncManager` thread which has a weak reference to underlying beacon @@ -261,7 +259,6 @@ pub fn spawn( beacon_processor: Arc>, sync_recv: mpsc::UnboundedReceiver>, fork_context: Arc, - log: slog::Logger, ) { assert!( beacon_chain.spec.max_request_blocks(fork_context.current_fork()) as u64 >= T::EthSpec::slots_per_epoch() * EPOCHS_PER_BATCH, @@ -276,12 +273,18 @@ pub fn spawn( sync_recv, SamplingConfig::Default, fork_context, - log.clone(), ); // spawn the sync manager thread - debug!(log, "Sync Manager started"); - executor.spawn(async move { Box::pin(sync_manager.main()).await }, "sync"); + debug!("Sync Manager started"); + executor.spawn( + async move { + Box::pin(sync_manager.main()) + .instrument(info_span!("", service = "sync")) + .await + }, + "sync", + ); } impl SyncManager { @@ -292,7 +295,6 @@ impl SyncManager { sync_recv: mpsc::UnboundedReceiver>, sampling_config: SamplingConfig, fork_context: Arc, - log: slog::Logger, ) -> Self { let network_globals = beacon_processor.network_globals.clone(); Self { @@ -303,23 +305,14 @@ impl SyncManager { beacon_processor.clone(), beacon_chain.clone(), fork_context.clone(), - log.clone(), ), - range_sync: RangeSync::new( - beacon_chain.clone(), - log.new(o!("service" => "range_sync")), - ), - backfill_sync: BackFillSync::new( - beacon_chain.clone(), - network_globals, - log.new(o!("service" => "backfill_sync")), - ), - block_lookups: BlockLookups::new(log.new(o!("service"=> "lookup_sync"))), + range_sync: RangeSync::new(beacon_chain.clone()), + backfill_sync: BackFillSync::new(beacon_chain.clone(), network_globals), + block_lookups: BlockLookups::new(), notified_unknown_roots: LRUTimeCache::new(Duration::from_secs( NOTIFIED_UNKNOWN_ROOT_EXPIRY_SECONDS, )), - sampling: Sampling::new(sampling_config, log.new(o!("service" => "sampling"))), - log: log.clone(), + sampling: Sampling::new(sampling_config), } } @@ -461,10 +454,10 @@ impl SyncManager { }; let head_slot = head_slot.unwrap_or_else(|| { - debug!(self.log, - "On add peers force range sync assuming local head_slot"; - "local_head_slot" => local.head_slot, - "head_root" => ?head_root + debug!( + local_head_slot = %local.head_slot, + ?head_root, + "On add peers force range sync assuming local head_slot" ); local.head_slot }); @@ -485,7 +478,7 @@ impl SyncManager { /// Handles RPC errors related to requests that were emitted from the sync manager. fn inject_error(&mut self, peer_id: PeerId, request_id: SyncRequestId, error: RPCError) { - trace!(self.log, "Sync manager received a failed RPC"); + trace!("Sync manager received a failed RPC"); match request_id { SyncRequestId::SingleBlock { id } => { self.on_single_block_response(id, peer_id, RpcEvent::RPCError(error)) @@ -565,15 +558,14 @@ impl SyncManager { let is_connected = self.network_globals().peers.read().is_connected(peer_id); if was_updated { debug!( - self.log, - "Peer transitioned sync state"; - "peer_id" => %peer_id, - "new_state" => rpr, - "our_head_slot" => local_sync_info.head_slot, - "our_finalized_epoch" => local_sync_info.finalized_epoch, - "their_head_slot" => remote_sync_info.head_slot, - "their_finalized_epoch" => remote_sync_info.finalized_epoch, - "is_connected" => is_connected + %peer_id, + new_state = rpr, + our_head_slot = %local_sync_info.head_slot, + our_finalized_epoch = %local_sync_info.finalized_epoch, + their_head_slot = %remote_sync_info.head_slot, + their_finalized_epoch = %remote_sync_info.finalized_epoch, + is_connected, + "Peer transitioned sync state" ); // A peer has transitioned its sync state. If the new state is "synced" we @@ -584,7 +576,7 @@ impl SyncManager { } is_connected } else { - error!(self.log, "Status'd peer is unknown"; "peer_id" => %peer_id); + error!(%peer_id, "Status'd peer is unknown"); false } } @@ -603,7 +595,7 @@ impl SyncManager { fn update_sync_state(&mut self) { let new_state: SyncState = match self.range_sync.state() { Err(e) => { - crit!(self.log, "Error getting range sync state"; "error" => %e); + crit!(error = %e, "Error getting range sync state"); return; } Ok(state) => match state { @@ -652,7 +644,7 @@ impl SyncManager { } Ok(SyncStart::NotSyncing) => {} // Ignore updating the state if the backfill sync state didn't start. Err(e) => { - error!(self.log, "Backfill sync failed to start"; "error" => ?e); + error!(error = ?e, "Backfill sync failed to start"); } } } @@ -686,7 +678,7 @@ impl SyncManager { let old_state = self.network_globals().set_sync_state(new_state); let new_state = self.network_globals().sync_state.read().clone(); if !new_state.eq(&old_state) { - info!(self.log, "Sync state updated"; "old_state" => %old_state, "new_state" => %new_state); + info!(%old_state, %new_state, "Sync state updated"); // If we have become synced - Subscribe to all the core subnet topics // We don't need to subscribe if the old state is a state that would have already // invoked this call. @@ -781,7 +773,7 @@ impl SyncManager { SyncMessage::UnknownParentBlock(peer_id, block, block_root) => { let block_slot = block.slot(); let parent_root = block.parent_root(); - debug!(self.log, "Received unknown parent block message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent block message"); self.handle_unknown_parent( peer_id, block_root, @@ -799,7 +791,7 @@ impl SyncManager { let blob_slot = blob.slot(); let block_root = blob.block_root(); let parent_root = blob.block_parent_root(); - debug!(self.log, "Received unknown parent blob message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent blob message"); self.handle_unknown_parent( peer_id, block_root, @@ -817,7 +809,7 @@ impl SyncManager { let data_column_slot = data_column.slot(); let block_root = data_column.block_root(); let parent_root = data_column.block_parent_root(); - debug!(self.log, "Received unknown parent data column message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent data column message"); self.handle_unknown_parent( peer_id, block_root, @@ -834,12 +826,12 @@ impl SyncManager { SyncMessage::UnknownBlockHashFromAttestation(peer_id, block_root) => { if !self.notified_unknown_roots.contains(&(peer_id, block_root)) { self.notified_unknown_roots.insert((peer_id, block_root)); - debug!(self.log, "Received unknown block hash message"; "block_root" => ?block_root, "peer" => ?peer_id); + debug!(?block_root, ?peer_id, "Received unknown block hash message"); self.handle_unknown_block_root(peer_id, block_root); } } SyncMessage::SampleBlock(block_root, block_slot) => { - debug!(self.log, "Received SampleBlock message"; "block_root" => %block_root, "slot" => block_slot); + debug!(%block_root, slot = %block_slot, "Received SampleBlock message"); if let Some((requester, result)) = self .sampling .on_new_sample_request(block_root, &mut self.network) @@ -848,7 +840,7 @@ impl SyncManager { } } SyncMessage::Disconnect(peer_id) => { - debug!(self.log, "Received disconnected message"; "peer_id" => %peer_id); + debug!(%peer_id, "Received disconnected message"); self.peer_disconnect(&peer_id); } SyncMessage::RpcError { @@ -889,7 +881,7 @@ impl SyncManager { Ok(ProcessResult::Successful) => {} Ok(ProcessResult::SyncCompleted) => self.update_sync_state(), Err(error) => { - error!(self.log, "Backfill sync failed"; "error" => ?error); + error!(error = ?error, "Backfill sync failed"); // Update the global status self.update_sync_state(); } @@ -925,7 +917,7 @@ impl SyncManager { ); } Err(reason) => { - debug!(self.log, "Ignoring unknown parent request"; "block_root" => %block_root, "parent_root" => %parent_root, "reason" => reason); + debug!(%block_root, %parent_root, reason, "Ignoring unknown parent request"); } } } @@ -937,7 +929,7 @@ impl SyncManager { .search_unknown_block(block_root, &[peer_id], &mut self.network); } Err(reason) => { - debug!(self.log, "Ignoring unknown block request"; "block_root" => %block_root, "reason" => reason); + debug!(%block_root, reason, "Ignoring unknown block request"); } } } @@ -1015,8 +1007,9 @@ impl SyncManager { // Some logs. if dropped_single_blocks_requests > 0 { - debug!(self.log, "Execution engine not online. Dropping active requests."; - "dropped_single_blocks_requests" => dropped_single_blocks_requests, + debug!( + dropped_single_blocks_requests, + "Execution engine not online. Dropping active requests." ); } } @@ -1042,7 +1035,7 @@ impl SyncManager { RpcEvent::from_chunk(block, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for block"; "peer_id" => %peer_id ); + crit!(%peer_id, "bad request id for block"); } } } @@ -1084,7 +1077,7 @@ impl SyncManager { RpcEvent::from_chunk(blob, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for blob"; "peer_id" => %peer_id); + crit!(%peer_id, "bad request id for blob"); } } } @@ -1110,7 +1103,7 @@ impl SyncManager { RpcEvent::from_chunk(data_column, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for data_column"; "peer_id" => %peer_id); + crit!(%peer_id, "bad request id for data_column"); } } } @@ -1174,7 +1167,7 @@ impl SyncManager { self.on_range_components_response( id.parent_request_id, peer_id, - RangeBlockComponent::Block(resp), + RangeBlockComponent::Block(id, resp), ); } } @@ -1189,7 +1182,7 @@ impl SyncManager { self.on_range_components_response( id.parent_request_id, peer_id, - RangeBlockComponent::Blob(resp), + RangeBlockComponent::Blob(id, resp), ); } } @@ -1207,7 +1200,7 @@ impl SyncManager { self.on_range_components_response( id.parent_request_id, peer_id, - RangeBlockComponent::CustodyColumns(resp), + RangeBlockComponent::CustodyColumns(id, resp), ); } } @@ -1228,7 +1221,7 @@ impl SyncManager { fn on_sampling_result(&mut self, requester: SamplingRequester, result: SamplingResult) { match requester { SamplingRequester::ImportedBlock(block_root) => { - debug!(self.log, "Sampling result"; "block_root" => %block_root, "result" => ?result); + debug!(%block_root, ?result, "Sampling result"); match result { Ok(_) => { @@ -1239,11 +1232,11 @@ impl SyncManager { .beacon_processor() .send_sampling_completed(block_root) { - warn!(self.log, "Error sending sampling result"; "block_root" => ?block_root, "reason" => ?e); + warn!(?block_root, reason = ?e, "Error sending sampling result"); } } Err(e) => { - warn!(self.log, "Sampling failed"; "block_root" => %block_root, "reason" => ?e); + warn!(?block_root, reason = ?e, "Sampling failed"); } } } @@ -1295,7 +1288,7 @@ impl SyncManager { } } } - Err(_) => match range_request_id.requester { + Err(e) => match range_request_id.requester { RangeRequestId::RangeSync { chain_id, batch_id } => { self.range_sync.inject_error( &mut self.network, @@ -1303,16 +1296,22 @@ impl SyncManager { batch_id, chain_id, range_request_id.id, + e, ); self.update_sync_state(); } - RangeRequestId::BackfillSync { batch_id } => match self - .backfill_sync - .inject_error(&mut self.network, batch_id, &peer_id, range_request_id.id) - { - Ok(_) => {} - Err(_) => self.update_sync_state(), - }, + RangeRequestId::BackfillSync { batch_id } => { + match self.backfill_sync.inject_error( + &mut self.network, + batch_id, + &peer_id, + range_request_id.id, + e, + ) { + Ok(_) => {} + Err(_) => self.update_sync_state(), + } + } }, } } diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index 968a9bcddd..16fcf93bcf 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -34,13 +34,13 @@ use requests::{ ActiveRequests, BlobsByRangeRequestItems, BlobsByRootRequestItems, BlocksByRangeRequestItems, BlocksByRootRequestItems, DataColumnsByRangeRequestItems, DataColumnsByRootRequestItems, }; -use slog::{debug, error, warn}; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; +use tracing::{debug, error, span, warn, Level}; use types::blob_sidecar::FixedBlobSidecarList; use types::{ BlobSidecar, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, ForkContext, @@ -74,10 +74,10 @@ pub type CustodyByRootResult = #[derive(Debug)] pub enum RpcResponseError { - RpcError(RPCError), + RpcError(#[allow(dead_code)] RPCError), VerifyError(LookupVerifyError), - CustodyRequestError(CustodyRequestError), - BlockComponentCouplingError(String), + CustodyRequestError(#[allow(dead_code)] CustodyRequestError), + BlockComponentCouplingError(#[allow(dead_code)] String), } #[derive(Debug, PartialEq, Eq)] @@ -89,6 +89,19 @@ pub enum RpcRequestSendError { SlotClockError, } +impl std::fmt::Display for RpcRequestSendError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + RpcRequestSendError::NetworkSendError => write!(f, "Network send error"), + RpcRequestSendError::NoCustodyPeers => write!(f, "No custody peers"), + RpcRequestSendError::CustodyRequestError(e) => { + write!(f, "Custody request error: {:?}", e) + } + RpcRequestSendError::SlotClockError => write!(f, "Slot clock error"), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum SendErrorProcessor { SendError, @@ -201,16 +214,22 @@ pub struct SyncNetworkContext { pub chain: Arc>, fork_context: Arc, - - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, } /// Small enumeration to make dealing with block and blob requests easier. pub enum RangeBlockComponent { - Block(RpcResponseResult>>>), - Blob(RpcResponseResult>>>), - CustodyColumns(RpcResponseResult>>>), + Block( + BlocksByRangeRequestId, + RpcResponseResult>>>, + ), + Blob( + BlobsByRangeRequestId, + RpcResponseResult>>>, + ), + CustodyColumns( + DataColumnsByRangeRequestId, + RpcResponseResult>>>, + ), } impl SyncNetworkContext { @@ -219,8 +238,13 @@ impl SyncNetworkContext { network_beacon_processor: Arc>, chain: Arc>, fork_context: Arc, - log: slog::Logger, ) -> Self { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); SyncNetworkContext { network_send, execution_engine_state: EngineState::Online, // always assume `Online` at the start @@ -236,7 +260,6 @@ impl SyncNetworkContext { network_beacon_processor, chain, fork_context, - log, } } @@ -267,7 +290,6 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, - log: _, } = self; let blocks_by_root_ids = blocks_by_root_requests @@ -330,17 +352,23 @@ impl SyncNetworkContext { } pub fn status_peers(&self, chain: &C, peers: impl Iterator) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let status_message = chain.status_message(); for peer_id in peers { debug!( - self.log, - "Sending Status Request"; - "peer" => %peer_id, - "fork_digest" => ?status_message.fork_digest, - "finalized_root" => ?status_message.finalized_root, - "finalized_epoch" => ?status_message.finalized_epoch, - "head_root" => %status_message.head_root, - "head_slot" => %status_message.head_slot, + peer = %peer_id, + fork_digest = ?status_message.fork_digest, + finalized_root = ?status_message.finalized_root, + finalized_epoch = ?status_message.finalized_epoch, + head_root = %status_message.head_root, + head_slot = %status_message.head_slot, + "Sending Status Request" ); let request = RequestType::Status(status_message.clone()); @@ -367,7 +395,16 @@ impl SyncNetworkContext { requester, }; - let _blocks_req_id = self.send_blocks_by_range_request(peer_id, request.clone(), id)?; + // Compute custody column peers before sending the blocks_by_range request. If we don't have + // enough peers, error here. + let data_column_requests = if matches!(batch_type, ByRangeRequestType::BlocksAndColumns) { + let column_indexes = self.network_globals().sampling_columns.clone(); + Some(self.make_columns_by_range_requests(request.clone(), &column_indexes)?) + } else { + None + }; + + let blocks_req_id = self.send_blocks_by_range_request(peer_id, request.clone(), id)?; let blobs_req_id = if matches!(batch_type, ByRangeRequestType::BlocksAndBlobs) { Some(self.send_blobs_by_range_request( @@ -382,36 +419,27 @@ impl SyncNetworkContext { None }; - let (expects_columns, data_column_requests) = - if matches!(batch_type, ByRangeRequestType::BlocksAndColumns) { - let column_indexes = self.network_globals().sampling_columns.clone(); + let data_columns = if let Some(data_column_requests) = data_column_requests { + let data_column_requests = data_column_requests + .into_iter() + .map(|(peer_id, columns_by_range_request)| { + self.send_data_columns_by_range_request(peer_id, columns_by_range_request, id) + }) + .collect::, _>>()?; - let data_column_requests = self - .make_columns_by_range_requests(request, &column_indexes)? - .into_iter() - .map(|(peer_id, columns_by_range_request)| { - self.send_data_columns_by_range_request( - peer_id, - columns_by_range_request, - id, - ) - }) - .collect::, _>>()?; + Some(( + data_column_requests, + self.network_globals() + .sampling_columns + .iter() + .cloned() + .collect::>(), + )) + } else { + None + }; - ( - Some(column_indexes.into_iter().collect::>()), - Some(data_column_requests), - ) - } else { - (None, None) - }; - - let expected_blobs = blobs_req_id.is_some(); - let info = RangeBlockComponentsRequest::new( - expected_blobs, - expects_columns, - data_column_requests.map(|items| items.len()), - ); + let info = RangeBlockComponentsRequest::new(blocks_req_id, blobs_req_id, data_columns); self.components_by_range_requests.insert(id, info); Ok(id.id) @@ -466,28 +494,33 @@ impl SyncNetworkContext { if let Err(e) = { let request = entry.get_mut(); match range_block_component { - RangeBlockComponent::Block(resp) => resp.map(|(blocks, _)| { - request.add_blocks(blocks); + RangeBlockComponent::Block(req_id, resp) => resp.and_then(|(blocks, _)| { + request + .add_blocks(req_id, blocks) + .map_err(RpcResponseError::BlockComponentCouplingError) }), - RangeBlockComponent::Blob(resp) => resp.map(|(blobs, _)| { - request.add_blobs(blobs); - }), - RangeBlockComponent::CustodyColumns(resp) => resp.map(|(custody_columns, _)| { - request.add_custody_columns(custody_columns); + RangeBlockComponent::Blob(req_id, resp) => resp.and_then(|(blobs, _)| { + request + .add_blobs(req_id, blobs) + .map_err(RpcResponseError::BlockComponentCouplingError) }), + RangeBlockComponent::CustodyColumns(req_id, resp) => { + resp.and_then(|(custody_columns, _)| { + request + .add_custody_columns(req_id, custody_columns) + .map_err(RpcResponseError::BlockComponentCouplingError) + }) + } } } { entry.remove(); return Some(Err(e)); } - if entry.get_mut().is_finished() { + if let Some(blocks_result) = entry.get().responses(&self.chain.spec) { + entry.remove(); // If the request is finished, dequeue everything - let request = entry.remove(); - let blocks = request - .into_responses(&self.chain.spec) - .map_err(RpcResponseError::BlockComponentCouplingError); - Some(blocks) + Some(blocks_result.map_err(RpcResponseError::BlockComponentCouplingError)) } else { None } @@ -518,6 +551,13 @@ impl SyncNetworkContext { return Ok(LookupRequestResult::Pending("no peers")); }; + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + match self.chain.get_block_process_status(&block_root) { // Unknown block, continue request to download BlockProcessStatus::Unknown => {} @@ -560,12 +600,11 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlocksByRoot", - "block_root" => ?block_root, - "peer" => %peer_id, - "id" => %id + method = "BlocksByRoot", + ?block_root, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blocks_by_root_requests.insert( @@ -608,6 +647,13 @@ impl SyncNetworkContext { return Ok(LookupRequestResult::Pending("no peers")); }; + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let imported_blob_indexes = self .chain .data_availability_checker @@ -643,13 +689,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlobsByRoot", - "block_root" => ?block_root, - "blob_indices" => ?indices, - "peer" => %peer_id, - "id" => %id + method = "BlobsByRoot", + ?block_root, + blob_indices = ?indices, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blobs_by_root_requests.insert( @@ -673,6 +718,13 @@ impl SyncNetworkContext { request: DataColumnsByRootSingleBlockRequest, expect_max_responses: bool, ) -> Result, &'static str> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let id = DataColumnsByRootRequestId { id: self.next_id(), requester, @@ -685,13 +737,12 @@ impl SyncNetworkContext { })?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "DataColumnsByRoot", - "block_root" => ?request.block_root, - "indices" => ?request.indices, - "peer" => %peer_id, - "id" => %id, + method = "DataColumnsByRoot", + block_root = ?request.block_root, + indices = ?request.indices, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.data_columns_by_root_requests.insert( @@ -714,6 +765,13 @@ impl SyncNetworkContext { block_root: Hash256, lookup_peers: Arc>>, ) -> Result { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let custody_indexes_imported = self .chain .data_availability_checker @@ -740,11 +798,10 @@ impl SyncNetworkContext { }; debug!( - self.log, - "Starting custody columns request"; - "block_root" => ?block_root, - "indices" => ?custody_indexes_to_fetch, - "id" => %id + ?block_root, + indices = ?custody_indexes_to_fetch, + %id, + "Starting custody columns request" ); let requester = CustodyRequester(id); @@ -753,7 +810,6 @@ impl SyncNetworkContext { CustodyId { requester }, &custody_indexes_to_fetch, lookup_peers, - self.log.clone(), ); // Note that you can only send, but not handle a response here @@ -788,13 +844,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlocksByRange", - "slots" => request.count(), - "epoch" => Slot::new(*request.start_slot()).epoch(T::EthSpec::slots_per_epoch()), - "peer" => %peer_id, - "id" => %id, + method = "BlocksByRange", + slots = request.count(), + epoch = %Slot::new(*request.start_slot()).epoch(T::EthSpec::slots_per_epoch()), + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blocks_by_range_requests.insert( @@ -830,13 +885,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlobsByRange", - "slots" => request.count, - "epoch" => request_epoch, - "peer" => %peer_id, - "id" => %id, + method = "BlobsByRange", + slots = request.count, + epoch = %request_epoch, + peer = %peer_id, + %id, + "Sync RPC request sent" ); let max_blobs_per_block = self.chain.spec.max_blobs_per_block(request_epoch); @@ -870,14 +924,13 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "DataColumnsByRange", - "slots" => request.count, - "epoch" => Slot::new(request.start_slot).epoch(T::EthSpec::slots_per_epoch()), - "columns" => ?request.columns, - "peer" => %peer_id, - "id" => %id, + method = "DataColumnsByRange", + slots = request.count, + epoch = %Slot::new(request.start_slot).epoch(T::EthSpec::slots_per_epoch()), + columns = ?request.columns, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.data_columns_by_range_requests.insert( @@ -896,13 +949,26 @@ impl SyncNetworkContext { } pub fn update_execution_engine_state(&mut self, engine_state: EngineState) { - debug!(self.log, "Sync's view on execution engine state updated"; - "past_state" => ?self.execution_engine_state, "new_state" => ?engine_state); + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + + debug!(past_state = ?self.execution_engine_state, new_state = ?engine_state, "Sync's view on execution engine state updated"); self.execution_engine_state = engine_state; } /// Terminates the connection with the peer and bans them. pub fn goodbye_peer(&mut self, peer_id: PeerId, reason: GoodbyeReason) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send .send(NetworkMessage::GoodbyePeer { peer_id, @@ -910,13 +976,20 @@ impl SyncNetworkContext { source: ReportSource::SyncService, }) .unwrap_or_else(|_| { - warn!(self.log, "Could not report peer: channel failed"); + warn!("Could not report peer: channel failed"); }); } /// Reports to the scoring algorithm the behaviour of a peer. pub fn report_peer(&self, peer_id: PeerId, action: PeerAction, msg: &'static str) { - debug!(self.log, "Sync reporting peer"; "peer_id" => %peer_id, "action" => %action, "msg" => %msg); + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + + debug!(%peer_id, %action, %msg, "Sync reporting peer"); self.network_send .send(NetworkMessage::ReportPeer { peer_id, @@ -925,23 +998,37 @@ impl SyncNetworkContext { msg, }) .unwrap_or_else(|e| { - warn!(self.log, "Could not report peer: channel failed"; "error"=> %e); + warn!(error = %e, "Could not report peer: channel failed"); }); } /// Subscribes to core topics. pub fn subscribe_core_topics(&self) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send .send(NetworkMessage::SubscribeCoreTopics) .unwrap_or_else(|e| { - warn!(self.log, "Could not subscribe to core topics."; "error" => %e); + warn!(error = %e, "Could not subscribe to core topics."); }); } /// Sends an arbitrary network message. fn send_network_msg(&self, msg: NetworkMessage) -> Result<(), &'static str> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send.send(msg).map_err(|_| { - debug!(self.log, "Could not send message to the network service"); + debug!("Could not send message to the network service"); "Network channel send Failed" }) } @@ -1128,20 +1215,18 @@ impl SyncNetworkContext { None => {} Some(Ok((v, _))) => { debug!( - self.log, - "Sync RPC request completed"; - "id" => %id, - "method" => method, - "count" => get_count(v) + %id, + method, + count = get_count(v), + "Sync RPC request completed" ); } Some(Err(e)) => { debug!( - self.log, - "Sync RPC request error"; - "id" => %id, - "method" => method, - "error" => ?e + %id, + method, + error = ?e, + "Sync RPC request error" ); } } @@ -1166,11 +1251,18 @@ impl SyncNetworkContext { peer_id: PeerId, resp: RpcResponseResult>>>, ) -> Option> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + // Note: need to remove the request to borrow self again below. Otherwise we can't // do nested requests let Some(mut request) = self.custody_by_root_requests.remove(&id.requester) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Custody column downloaded event for unknown request"; "id" => ?id); + debug!(?id, "Custody column downloaded event for unknown request"); return None; }; @@ -1185,6 +1277,13 @@ impl SyncNetworkContext { request: ActiveCustodyRequest, result: CustodyRequestResult, ) -> Option> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let result = result .map_err(RpcResponseError::CustodyRequestError) .transpose(); @@ -1193,10 +1292,10 @@ impl SyncNetworkContext { // an Option first to use in an `if let Some() { act on result }` block. match result.as_ref() { Some(Ok((columns, peer_group, _))) => { - debug!(self.log, "Custody request success, removing"; "id" => ?id, "count" => columns.len(), "peers" => ?peer_group) + debug!(?id, count = columns.len(), peers = ?peer_group, "Custody request success, removing") } Some(Err(e)) => { - debug!(self.log, "Custody request failure, removing"; "id" => ?id, "error" => ?e) + debug!(?id, error = ?e, "Custody request failure, removing" ) } None => { self.custody_by_root_requests.insert(id, request); @@ -1212,11 +1311,18 @@ impl SyncNetworkContext { block: RpcBlock, seen_timestamp: Duration, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending block for processing"; "block" => ?block_root, "id" => id); + debug!(block = ?block_root, id, "Sending block for processing"); // Lookup sync event safety: If `beacon_processor.send_rpc_beacon_block` returns Ok() sync // must receive a single `SyncMessage::BlockComponentProcessed` with this process type beacon_processor @@ -1228,9 +1334,8 @@ impl SyncNetworkContext { ) .map_err(|e| { error!( - self.log, - "Failed to send sync block to processor"; - "error" => ?e + error = ?e, + "Failed to send sync block to processor" ); SendErrorProcessor::SendError }) @@ -1243,11 +1348,18 @@ impl SyncNetworkContext { blobs: FixedBlobSidecarList, seen_timestamp: Duration, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending blobs for processing"; "block" => ?block_root, "id" => id); + debug!(?block_root, ?id, "Sending blobs for processing"); // Lookup sync event safety: If `beacon_processor.send_rpc_blobs` returns Ok() sync // must receive a single `SyncMessage::BlockComponentProcessed` event with this process type beacon_processor @@ -1259,9 +1371,8 @@ impl SyncNetworkContext { ) .map_err(|e| { error!( - self.log, - "Failed to send sync blobs to processor"; - "error" => ?e + error = ?e, + "Failed to send sync blobs to processor" ); SendErrorProcessor::SendError }) @@ -1275,19 +1386,29 @@ impl SyncNetworkContext { seen_timestamp: Duration, process_type: BlockProcessType, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending custody columns for processing"; "block" => ?block_root, "process_type" => ?process_type); + debug!( + ?block_root, + ?process_type, + "Sending custody columns for processing" + ); beacon_processor .send_rpc_custody_columns(block_root, custody_columns, seen_timestamp, process_type) .map_err(|e| { error!( - self.log, - "Failed to send sync custody columns to processor"; - "error" => ?e + error = ?e, + "Failed to send sync custody columns to processor" ); SendErrorProcessor::SendError }) diff --git a/beacon_node/network/src/sync/network_context/custody.rs b/beacon_node/network/src/sync/network_context/custody.rs index 38353d3ea2..018381a850 100644 --- a/beacon_node/network/src/sync/network_context/custody.rs +++ b/beacon_node/network/src/sync/network_context/custody.rs @@ -9,10 +9,10 @@ use lighthouse_network::PeerId; use lru_cache::LRUTimeCache; use parking_lot::RwLock; use rand::Rng; -use slog::{debug, warn}; use std::collections::HashSet; use std::time::{Duration, Instant}; use std::{collections::HashMap, marker::PhantomData, sync::Arc}; +use tracing::{debug, warn}; use types::EthSpec; use types::{data_column_sidecar::ColumnIndex, DataColumnSidecar, Hash256}; @@ -36,8 +36,7 @@ pub struct ActiveCustodyRequest { failed_peers: LRUTimeCache, /// Set of peers that claim to have imported this block and their custody columns lookup_peers: Arc>>, - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, + _phantom: PhantomData, } @@ -70,7 +69,6 @@ impl ActiveCustodyRequest { custody_id: CustodyId, column_indices: &[ColumnIndex], lookup_peers: Arc>>, - log: slog::Logger, ) -> Self { Self { block_root, @@ -83,7 +81,6 @@ impl ActiveCustodyRequest { active_batch_columns_requests: <_>::default(), failed_peers: LRUTimeCache::new(Duration::from_secs(FAILED_PEERS_CACHE_EXPIRY_SECONDS)), lookup_peers, - log, _phantom: PhantomData, } } @@ -104,24 +101,24 @@ impl ActiveCustodyRequest { cx: &mut SyncNetworkContext, ) -> CustodyRequestResult { let Some(batch_request) = self.active_batch_columns_requests.get_mut(&req_id) else { - warn!(self.log, - "Received custody column response for unrequested index"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, + warn!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + "Received custody column response for unrequested index" ); return Ok(None); }; match resp { Ok((data_columns, seen_timestamp)) => { - debug!(self.log, - "Custody column download success"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, - "count" => data_columns.len() + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, + count = data_columns.len(), + "Custody column download success" ); // Map columns by index as an optimization to not loop the returned list on each @@ -163,27 +160,27 @@ impl ActiveCustodyRequest { if !missing_column_indexes.is_empty() { // Note: Batch logging that columns are missing to not spam logger - debug!(self.log, - "Custody column peer claims to not have some data"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, // TODO(das): this property can become very noisy, being the full range 0..128 - "missing_column_indexes" => ?missing_column_indexes + ?missing_column_indexes, + "Custody column peer claims to not have some data" ); self.failed_peers.insert(peer_id); } } Err(err) => { - debug!(self.log, - "Custody column download error"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, - "error" => ?err + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, + error = ?err, + "Custody column download error" ); // TODO(das): Should mark peer as failed and try from another peer diff --git a/beacon_node/network/src/sync/peer_sampling.rs b/beacon_node/network/src/sync/peer_sampling.rs index 289ed73cdd..59b751787e 100644 --- a/beacon_node/network/src/sync/peer_sampling.rs +++ b/beacon_node/network/src/sync/peer_sampling.rs @@ -12,11 +12,11 @@ use lighthouse_network::service::api_types::{ }; use lighthouse_network::{PeerAction, PeerId}; use rand::{seq::SliceRandom, thread_rng}; -use slog::{debug, error, warn}; use std::{ collections::hash_map::Entry, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration, }; +use tracing::{debug, error, instrument, warn}; use types::{data_column_sidecar::ColumnIndex, ChainSpec, DataColumnSidecar, Hash256}; pub type SamplingResult = Result<(), SamplingError>; @@ -26,24 +26,35 @@ type DataColumnSidecarList = Vec>>; pub struct Sampling { requests: HashMap>, sampling_config: SamplingConfig, - log: slog::Logger, } impl Sampling { - pub fn new(sampling_config: SamplingConfig, log: slog::Logger) -> Self { + #[instrument(parent = None,level = "info", fields(service = "sampling"), name = "sampling")] + pub fn new(sampling_config: SamplingConfig) -> Self { Self { requests: <_>::default(), sampling_config, - log, } } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn active_sampling_requests(&self) -> Vec { self.requests.values().map(|r| r.block_root).collect() } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn get_request_status( &self, block_root: Hash256, @@ -61,6 +72,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_new_sample_request( &mut self, block_root: Hash256, @@ -73,7 +90,6 @@ impl Sampling { block_root, id, &self.sampling_config, - self.log.clone(), &cx.chain.spec, )), Entry::Occupied(_) => { @@ -82,15 +98,15 @@ impl Sampling { // TODO(das): Should track failed sampling request for some time? Otherwise there's // a risk of a loop with multiple triggers creating the request, then failing, // and repeat. - debug!(self.log, "Ignoring duplicate sampling request"; "id" => ?id); + debug!(?id, "Ignoring duplicate sampling request"); return None; } }; - debug!(self.log, - "Created new sample request"; - "id" => ?id, - "column_selection" => ?request.column_selection() + debug!( + ?id, + column_selection = ?request.column_selection(), + "Created new sample request" ); // TOOD(das): If a node has very little peers, continue_sampling() will attempt to find enough @@ -107,6 +123,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_sample_downloaded( &mut self, id: SamplingId, @@ -116,7 +138,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let Some(request) = self.requests.get_mut(&id.id) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Sample downloaded event for unknown request"; "id" => ?id); + debug!(?id, "Sample downloaded event for unknown request"); return None; }; @@ -131,6 +153,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_sample_verified( &mut self, id: SamplingId, @@ -139,7 +167,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let Some(request) = self.requests.get_mut(&id.id) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Sample verified event for unknown request"; "id" => ?id); + debug!(?id, "Sample verified event for unknown request"); return None; }; @@ -150,6 +178,12 @@ impl Sampling { /// Converts a result from the internal format of `ActiveSamplingRequest` (error first to use ? /// conveniently), to an Option first format to use an `if let Some() { act on result }` pattern /// in the sync manager. + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] fn handle_sampling_result( &mut self, result: Result, SamplingError>, @@ -157,7 +191,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let result = result.transpose(); if let Some(result) = result { - debug!(self.log, "Sampling request completed, removing"; "id" => ?id, "result" => ?result); + debug!(?id, ?result, "Sampling request completed, removing"); metrics::inc_counter_vec( &metrics::SAMPLING_REQUEST_RESULT, &[metrics::from_result(&result)], @@ -180,8 +214,6 @@ pub struct ActiveSamplingRequest { current_sampling_request_id: SamplingRequestId, column_shuffle: Vec, required_successes: Vec, - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, _phantom: PhantomData, } @@ -212,7 +244,6 @@ impl ActiveSamplingRequest { block_root: Hash256, requester_id: SamplingRequester, sampling_config: &SamplingConfig, - log: slog::Logger, spec: &ChainSpec, ) -> Self { // Select ahead of time the full list of to-sample columns @@ -232,7 +263,6 @@ impl ActiveSamplingRequest { SamplingConfig::Default => REQUIRED_SUCCESSES.to_vec(), SamplingConfig::Custom { required_successes } => required_successes.clone(), }, - log, _phantom: PhantomData, } } @@ -275,9 +305,9 @@ impl ActiveSamplingRequest { .column_indexes_by_sampling_request .get(&sampling_request_id) else { - error!(self.log, - "Column indexes for the sampling request ID not found"; - "sampling_request_id" => ?sampling_request_id + error!( + ?sampling_request_id, + "Column indexes for the sampling request ID not found" ); return Ok(None); }; @@ -288,11 +318,11 @@ impl ActiveSamplingRequest { .iter() .map(|r| r.index) .collect::>(); - debug!(self.log, - "Sample download success"; - "block_root" => %self.block_root, - "column_indexes" => ?resp_column_indexes, - "count" => resp_data_columns.len() + debug!( + block_root = %self.block_root, + column_indexes = ?resp_column_indexes, + count = resp_data_columns.len(), + "Sample download success" ); metrics::inc_counter_vec(&metrics::SAMPLE_DOWNLOAD_RESULT, &[metrics::SUCCESS]); @@ -300,10 +330,10 @@ impl ActiveSamplingRequest { let mut data_columns = vec![]; for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { - warn!(self.log, - "Active column sample request not found"; - "block_root" => %self.block_root, - "column_index" => column_index + warn!( + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -314,10 +344,10 @@ impl ActiveSamplingRequest { else { // Peer does not have the requested data, mark peer as "dont have" and try // again with a different peer. - debug!(self.log, - "Sampling peer claims to not have the data"; - "block_root" => %self.block_root, - "column_index" => column_index + debug!( + block_root = %self.block_root, + column_index, + "Sampling peer claims to not have the data" ); request.on_sampling_error()?; continue; @@ -331,16 +361,16 @@ impl ActiveSamplingRequest { .iter() .map(|d| d.index) .collect::>(); - debug!(self.log, - "Received data that was not requested"; - "block_root" => %self.block_root, - "column_indexes" => ?resp_column_indexes + debug!( + block_root = %self.block_root, + column_indexes = ?resp_column_indexes, + "Received data that was not requested" ); } // Handle the downloaded data columns. if data_columns.is_empty() { - debug!(self.log, "Received empty response"; "block_root" => %self.block_root); + debug!(block_root = %self.block_root, "Received empty response"); self.column_indexes_by_sampling_request .remove(&sampling_request_id); } else { @@ -351,17 +381,17 @@ impl ActiveSamplingRequest { // Peer has data column, send to verify let Some(beacon_processor) = cx.beacon_processor_if_enabled() else { // If processor is not available, error the entire sampling - debug!(self.log, - "Dropping sampling"; - "block" => %self.block_root, - "reason" => "beacon processor unavailable" + debug!( + block = %self.block_root, + reason = "beacon processor unavailable", + "Dropping sampling" ); return Err(SamplingError::ProcessorUnavailable); }; - debug!(self.log, - "Sending data_column for verification"; - "block" => ?self.block_root, - "column_indexes" => ?column_indexes + debug!( + block = ?self.block_root, + ?column_indexes, + "Sending data_column for verification" ); if let Err(e) = beacon_processor.send_rpc_validate_data_columns( self.block_root, @@ -375,20 +405,21 @@ impl ActiveSamplingRequest { // Beacon processor is overloaded, drop sampling attempt. Failing to sample // is not a permanent state so we should recover once the node has capacity // and receives a descendant block. - error!(self.log, - "Dropping sampling"; - "block" => %self.block_root, - "reason" => e.to_string() + error!( + block = %self.block_root, + reason = e.to_string(), + "Dropping sampling" ); return Err(SamplingError::SendFailed("beacon processor send failure")); } } } Err(err) => { - debug!(self.log, "Sample download error"; - "block_root" => %self.block_root, - "column_indexes" => ?column_indexes, - "error" => ?err + debug!( + block_root = %self.block_root, + ?column_indexes, + error = ?err, + "Sample download error" ); metrics::inc_counter_vec(&metrics::SAMPLE_DOWNLOAD_RESULT, &[metrics::FAILURE]); @@ -396,10 +427,10 @@ impl ActiveSamplingRequest { // reaching this function. Mark the peer as failed and try again with another. for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { - warn!(self.log, - "Active column sample request not found"; - "block_root" => %self.block_root, - "column_index" => column_index + warn!( + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -429,21 +460,24 @@ impl ActiveSamplingRequest { .column_indexes_by_sampling_request .get(&sampling_request_id) else { - error!(self.log, "Column indexes for the sampling request ID not found"; "sampling_request_id" => ?sampling_request_id); + error!( + ?sampling_request_id, + "Column indexes for the sampling request ID not found" + ); return Ok(None); }; match result { Ok(_) => { - debug!(self.log, "Sample verification success"; "block_root" => %self.block_root, "column_indexes" => ?column_indexes); + debug!(block_root = %self.block_root,?column_indexes, "Sample verification success"); metrics::inc_counter_vec(&metrics::SAMPLE_VERIFY_RESULT, &[metrics::SUCCESS]); // Valid, continue_sampling will maybe consider sampling succees for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { warn!( - self.log, - "Active column sample request not found"; "block_root" => %self.block_root, "column_index" => column_index + block_root = %self.block_root, column_index, + "Active column sample request not found" ); continue; }; @@ -451,7 +485,7 @@ impl ActiveSamplingRequest { } } Err(err) => { - debug!(self.log, "Sample verification failure"; "block_root" => %self.block_root, "column_indexes" => ?column_indexes, "reason" => ?err); + debug!(block_root = %self.block_root, ?column_indexes, reason = ?err, "Sample verification failure"); metrics::inc_counter_vec(&metrics::SAMPLE_VERIFY_RESULT, &[metrics::FAILURE]); // Peer sent invalid data, penalize and try again from different peer @@ -459,8 +493,9 @@ impl ActiveSamplingRequest { for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { warn!( - self.log, - "Active column sample request not found"; "block_root" => %self.block_root, "column_index" => column_index + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -570,7 +605,7 @@ impl ActiveSamplingRequest { // request was sent, loop to increase the required_successes until the sampling fails if // there are no peers. if ongoings == 0 && !sent_request { - debug!(self.log, "Sampling request stalled"; "block_root" => %self.block_root); + debug!(block_root = %self.block_root, "Sampling request stalled"); } Ok(None) diff --git a/beacon_node/network/src/sync/range_sync/batch.rs b/beacon_node/network/src/sync/range_sync/batch.rs index 912287a8a4..c1ad550376 100644 --- a/beacon_node/network/src/sync/range_sync/batch.rs +++ b/beacon_node/network/src/sync/range_sync/batch.rs @@ -3,6 +3,7 @@ use lighthouse_network::rpc::methods::BlocksByRangeRequest; use lighthouse_network::service::api_types::Id; use lighthouse_network::PeerId; use std::collections::HashSet; +use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Sub; use std::time::{Duration, Instant}; @@ -61,6 +62,7 @@ pub trait BatchConfig { fn batch_attempt_hash(blocks: &[RpcBlock]) -> u64; } +#[derive(Debug)] pub struct RangeSyncBatchConfig {} impl BatchConfig for RangeSyncBatchConfig { @@ -93,6 +95,7 @@ pub enum BatchProcessingResult { NonFaultyFailure, } +#[derive(Debug)] /// A segment of a chain. pub struct BatchInfo { /// Start slot of the batch. @@ -113,6 +116,17 @@ pub struct BatchInfo { marker: std::marker::PhantomData, } +impl fmt::Display for BatchInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Start Slot: {}, End Slot: {}, State: {}", + self.start_slot, self.end_slot, self.state + ) + } +} + +#[derive(Display)] /// Current state of a batch pub enum BatchState { /// The batch has failed either downloading or processing, but can be requested again. @@ -190,15 +204,6 @@ impl BatchInfo { peers } - /// Return the number of times this batch has failed downloading and failed processing, in this - /// order. - pub fn failed_attempts(&self) -> (usize, usize) { - ( - self.failed_download_attempts.len(), - self.failed_processing_attempts.len(), - ) - } - /// Verifies if an incoming block belongs to this batch. pub fn is_expecting_block(&self, request_id: &Id) -> bool { if let BatchState::Downloading(_, expected_id) = &self.state { @@ -456,39 +461,6 @@ impl Attempt { } } -impl slog::KV for &mut BatchInfo { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::KV::serialize(*self, record, serializer) - } -} - -impl slog::KV for BatchInfo { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - Value::serialize(&self.start_slot, record, "start_slot", serializer)?; - Value::serialize( - &(self.end_slot - 1), // NOTE: The -1 shows inclusive blocks - record, - "end_slot", - serializer, - )?; - serializer.emit_usize("downloaded", self.failed_download_attempts.len())?; - serializer.emit_usize("processed", self.failed_processing_attempts.len())?; - serializer.emit_u8("processed_no_penalty", self.non_faulty_processing_attempts)?; - serializer.emit_arguments("state", &format_args!("{:?}", self.state))?; - serializer.emit_arguments("batch_ty", &format_args!("{}", self.batch_type))?; - slog::Result::Ok(()) - } -} - impl std::fmt::Debug for BatchState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/beacon_node/network/src/sync/range_sync/chain.rs b/beacon_node/network/src/sync/range_sync/chain.rs index cab08dd278..24045e901b 100644 --- a/beacon_node/network/src/sync/range_sync/chain.rs +++ b/beacon_node/network/src/sync/range_sync/chain.rs @@ -2,18 +2,20 @@ use super::batch::{BatchInfo, BatchProcessingResult, BatchState}; use super::RangeSyncType; use crate::metrics; use crate::network_beacon_processor::ChainSegmentProcessId; -use crate::sync::network_context::RangeRequestId; +use crate::sync::network_context::{RangeRequestId, RpcResponseError}; use crate::sync::{network_context::SyncNetworkContext, BatchOperationOutcome, BatchProcessResult}; use beacon_chain::block_verification_types::RpcBlock; use beacon_chain::BeaconChainTypes; use fnv::FnvHashMap; use lighthouse_network::service::api_types::Id; use lighthouse_network::{PeerAction, PeerId}; +use logging::crit; use rand::seq::SliceRandom; use rand::Rng; -use slog::{crit, debug, o, warn}; use std::collections::{btree_map::Entry, BTreeMap, HashSet}; +use std::fmt; use strum::IntoStaticStr; +use tracing::{debug, instrument, warn}; use types::{Epoch, EthSpec, Hash256, Slot}; /// Blocks are downloaded in batches from peers. This constant specifies how many epochs worth of @@ -37,6 +39,7 @@ pub type ProcessingResult = Result; /// Reasons for removing a chain #[derive(Debug)] +#[allow(dead_code)] pub enum RemoveChain { EmptyPeerPool, ChainCompleted, @@ -66,6 +69,7 @@ pub enum SyncingChainType { /// A chain of blocks that need to be downloaded. Peers who claim to contain the target head /// root are grouped into the peer pool and queried for batches when downloading the /// chain. +#[derive(Debug)] pub struct SyncingChain { /// A random id used to identify this chain. id: ChainId, @@ -110,9 +114,16 @@ pub struct SyncingChain { /// The current processing batch, if any. current_processing_batch: Option, +} - /// The chain's log. - log: slog::Logger, +impl fmt::Display for SyncingChain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.chain_type { + SyncingChainType::Head => write!(f, "Head"), + SyncingChainType::Finalized => write!(f, "Finalized"), + SyncingChainType::Backfill => write!(f, "Backfill"), + } + } } #[derive(PartialEq, Debug)] @@ -132,7 +143,6 @@ impl SyncingChain { target_head_root: Hash256, peer_id: PeerId, chain_type: SyncingChainType, - log: &slog::Logger, ) -> Self { let mut peers = FnvHashMap::default(); peers.insert(peer_id, Default::default()); @@ -151,7 +161,6 @@ impl SyncingChain { attempted_optimistic_starts: HashSet::default(), state: ChainSyncingState::Stopped, current_processing_batch: None, - log: log.new(o!("chain" => id)), } } @@ -161,21 +170,25 @@ impl SyncingChain { } /// Check if the chain has peers from which to process batches. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn available_peers(&self) -> usize { self.peers.len() } /// Get the chain's id. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn get_id(&self) -> ChainId { self.id } /// Peers currently syncing this chain. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn peers(&self) -> impl Iterator + '_ { self.peers.keys().cloned() } /// Progress in epochs made by the chain + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn processed_epochs(&self) -> u64 { self.processing_target .saturating_sub(self.start_epoch) @@ -183,6 +196,7 @@ impl SyncingChain { } /// Returns the total count of pending blocks in all the batches of this chain + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn pending_blocks(&self) -> usize { self.batches .values() @@ -192,6 +206,7 @@ impl SyncingChain { /// Removes a peer from the chain. /// If the peer has active batches, those are considered failed and re-requested. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn remove_peer( &mut self, peer_id: &PeerId, @@ -211,8 +226,7 @@ impl SyncingChain { } self.retry_batch_download(network, id)?; } else { - debug!(self.log, "Batch not found while removing peer"; - "peer" => %peer_id, "batch" => id) + debug!(%peer_id, batch = ?id, "Batch not found while removing peer") } } } @@ -225,6 +239,7 @@ impl SyncingChain { } /// Returns the latest slot number that has been processed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn current_processed_slot(&self) -> Slot { // the last slot we processed was included in the previous batch, and corresponds to the // first slot of the current target epoch @@ -234,6 +249,7 @@ impl SyncingChain { /// A block has been received for a batch on this chain. /// If the block correctly completes the batch it will be processed if possible. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn on_block_response( &mut self, network: &mut SyncNetworkContext, @@ -245,7 +261,7 @@ impl SyncingChain { // check if we have this batch let batch = match self.batches.get_mut(&batch_id) { None => { - debug!(self.log, "Received a block for unknown batch"; "epoch" => batch_id); + debug!(epoch = %batch_id, "Received a block for unknown batch"); // A batch might get removed when the chain advances, so this is non fatal. return Ok(KeepChain); } @@ -273,7 +289,7 @@ impl SyncingChain { let awaiting_batches = batch_id .saturating_sub(self.optimistic_start.unwrap_or(self.processing_target)) / EPOCHS_PER_BATCH; - debug!(self.log, "Batch downloaded"; "epoch" => batch_id, "blocks" => received, "batch_state" => self.visualize_batch_state(), "awaiting_batches" => awaiting_batches); + debug!(epoch = %batch_id, blocks = received, batch_state = self.visualize_batch_state(), %awaiting_batches,"Batch downloaded"); // pre-emptively request more blocks from peers whilst we process current blocks, self.request_batches(network)?; @@ -282,6 +298,7 @@ impl SyncingChain { /// Processes the batch with the given id. /// The batch must exist and be ready for processing + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn process_batch( &mut self, network: &mut SyncNetworkContext, @@ -317,8 +334,7 @@ impl SyncingChain { self.current_processing_batch = Some(batch_id); if let Err(e) = beacon_processor.send_chain_segment(process_id, blocks) { - crit!(self.log, "Failed to send chain segment to processor."; "msg" => "process_batch", - "error" => %e, "batch" => self.processing_target); + crit!(msg = "process_batch",error = %e, batch = ?self.processing_target, "Failed to send chain segment to processor."); // This is unlikely to happen but it would stall syncing since the batch now has no // blocks to continue, and the chain is expecting a processing result that won't // arrive. To mitigate this, (fake) fail this processing so that the batch is @@ -330,6 +346,7 @@ impl SyncingChain { } /// Processes the next ready batch, prioritizing optimistic batches over the processing target. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn process_completed_batches( &mut self, network: &mut SyncNetworkContext, @@ -349,7 +366,7 @@ impl SyncingChain { match state { BatchState::AwaitingProcessing(..) => { // this batch is ready - debug!(self.log, "Processing optimistic start"; "epoch" => epoch); + debug!(%epoch, "Processing optimistic start"); return self.process_batch(network, epoch); } BatchState::Downloading(..) => { @@ -377,7 +394,7 @@ impl SyncingChain { // batch has been requested and processed we can land here. We drop the // optimistic candidate since we can't conclude whether the batch included // blocks or not at this point - debug!(self.log, "Dropping optimistic candidate"; "batch" => epoch); + debug!(batch = %epoch, "Dropping optimistic candidate"); self.optimistic_start = None; } } @@ -411,7 +428,10 @@ impl SyncingChain { // inside the download buffer (between `self.processing_target` and // `self.to_be_downloaded`). In this case, eventually the chain advances to the // batch (`self.processing_target` reaches this point). - debug!(self.log, "Chain encountered a robust batch awaiting validation"; "batch" => self.processing_target); + debug!( + batch = %self.processing_target, + "Chain encountered a robust batch awaiting validation" + ); self.processing_target += EPOCHS_PER_BATCH; if self.to_be_downloaded <= self.processing_target { @@ -436,6 +456,7 @@ impl SyncingChain { /// The block processor has completed processing a batch. This function handles the result /// of the batch processor. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn on_batch_process_result( &mut self, network: &mut SyncNetworkContext, @@ -447,13 +468,11 @@ impl SyncingChain { let batch_state = self.visualize_batch_state(); let batch = match &self.current_processing_batch { Some(processing_id) if *processing_id != batch_id => { - debug!(self.log, "Unexpected batch result"; - "batch_epoch" => batch_id, "expected_batch_epoch" => processing_id); + debug!(batch_epoch = %batch_id, expected_batch_epoch = %processing_id,"Unexpected batch result"); return Ok(KeepChain); } None => { - debug!(self.log, "Chain was not expecting a batch result"; - "batch_epoch" => batch_id); + debug!(batch_epoch = %batch_id,"Chain was not expecting a batch result"); return Ok(KeepChain); } _ => { @@ -476,8 +495,14 @@ impl SyncingChain { })?; // Log the process result and the batch for debugging purposes. - debug!(self.log, "Batch processing result"; "result" => ?result, &batch, - "batch_epoch" => batch_id, "client" => %network.client_type(&peer), "batch_state" => batch_state); + debug!( + result = ?result, + batch_epoch = %batch_id, + client = %network.client_type(&peer), + batch_state = ?batch_state, + ?batch, + "Batch processing result" + ); // We consider three cases. Batch was successfully processed, Batch failed processing due // to a faulty peer, or batch failed processing but the peer can't be deemed faulty. @@ -563,10 +588,9 @@ impl SyncingChain { // There are some edge cases with forks that could land us in this situation. // This should be unlikely, so we tolerate these errors, but not often. warn!( - self.log, - "Batch failed to download. Dropping chain scoring peers"; - "score_adjustment" => %penalty, - "batch_epoch"=> batch_id, + score_adjustment = %penalty, + batch_epoch = %batch_id, + "Batch failed to download. Dropping chain scoring peers" ); for (peer, _) in self.peers.drain() { @@ -587,6 +611,7 @@ impl SyncingChain { } } + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn reject_optimistic_batch( &mut self, network: &mut SyncNetworkContext, @@ -599,13 +624,13 @@ impl SyncingChain { // it. NOTE: this is done to prevent non-sequential batches coming from optimistic // starts from filling up the buffer size if epoch < self.to_be_downloaded { - debug!(self.log, "Rejected optimistic batch left for future use"; "epoch" => %epoch, "reason" => reason); + debug!(%epoch, reason, "Rejected optimistic batch left for future use"); // this batch is now treated as any other batch, and re-requested for future use if redownload { return self.retry_batch_download(network, epoch); } } else { - debug!(self.log, "Rejected optimistic batch"; "epoch" => %epoch, "reason" => reason); + debug!(%epoch, reason, "Rejected optimistic batch"); self.batches.remove(&epoch); } } @@ -621,6 +646,7 @@ impl SyncingChain { /// If a previous batch has been validated and it had been re-processed, penalize the original /// peer. #[allow(clippy::modulo_one)] + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn advance_chain(&mut self, network: &mut SyncNetworkContext, validating_epoch: Epoch) { // make sure this epoch produces an advancement if validating_epoch <= self.start_epoch { @@ -629,7 +655,7 @@ impl SyncingChain { // safety check for batch boundaries if validating_epoch % EPOCHS_PER_BATCH != self.start_epoch % EPOCHS_PER_BATCH { - crit!(self.log, "Validating Epoch is not aligned"); + crit!("Validating Epoch is not aligned"); return; } @@ -651,9 +677,10 @@ impl SyncingChain { // A different peer sent the correct batch, the previous peer did not // We negatively score the original peer. let action = PeerAction::LowToleranceError; - debug!(self.log, "Re-processed batch validated. Scoring original peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = %id, score_adjustment = %action, + original_peer = %attempt.peer_id, new_peer = %processed_attempt.peer_id, + "Re-processed batch validated. Scoring original peer" ); network.report_peer( attempt.peer_id, @@ -664,9 +691,12 @@ impl SyncingChain { // The same peer corrected it's previous mistake. There was an error, so we // negative score the original peer. let action = PeerAction::MidToleranceError; - debug!(self.log, "Re-processed batch validated by the same peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = %id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated by the same peer" ); network.report_peer( attempt.peer_id, @@ -683,13 +713,12 @@ impl SyncingChain { active_batches.remove(&id); } } - BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => crit!( - self.log, - "batch indicates inconsistent chain state while advancing chain" - ), + BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => { + crit!("batch indicates inconsistent chain state while advancing chain") + } BatchState::AwaitingProcessing(..) => {} BatchState::Processing(_) => { - debug!(self.log, "Advancing chain while processing a batch"; "batch" => id, batch); + debug!(batch = %id, %batch, "Advancing chain while processing a batch"); if let Some(processing_id) = self.current_processing_batch { if id <= processing_id { self.current_processing_batch = None; @@ -713,8 +742,12 @@ impl SyncingChain { self.optimistic_start = None; } } - debug!(self.log, "Chain advanced"; "previous_start" => old_start, - "new_start" => self.start_epoch, "processing_target" => self.processing_target); + debug!( + previous_start = %old_start, + new_start = %self.start_epoch, + processing_target = %self.processing_target, + "Chain advanced" + ); } /// An invalid batch has been received that could not be processed, but that can be retried. @@ -722,6 +755,7 @@ impl SyncingChain { /// These events occur when a peer has successfully responded with blocks, but the blocks we /// have received are incorrect or invalid. This indicates the peer has not performed as /// intended and can result in downvoting a peer. + #[instrument(parent = None,level = "info", fields(service = self.id, network), skip_all)] fn handle_invalid_batch( &mut self, network: &mut SyncNetworkContext, @@ -781,6 +815,7 @@ impl SyncingChain { /// This chain has been requested to start syncing. /// /// This could be new chain, or an old chain that is being resumed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn start_syncing( &mut self, network: &mut SyncNetworkContext, @@ -819,6 +854,7 @@ impl SyncingChain { /// Add a peer to the chain. /// /// If the chain is active, this starts requesting batches from this peer. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn add_peer( &mut self, network: &mut SyncNetworkContext, @@ -836,12 +872,14 @@ impl SyncingChain { /// An RPC error has occurred. /// /// If the batch exists it is re-requested. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn inject_error( &mut self, network: &mut SyncNetworkContext, batch_id: BatchId, peer_id: &PeerId, request_id: Id, + err: RpcResponseError, ) -> ProcessingResult { let batch_state = self.visualize_batch_state(); if let Some(batch) = self.batches.get_mut(&batch_id) { @@ -852,24 +890,22 @@ impl SyncingChain { // columns. if !batch.is_expecting_block(&request_id) { debug!( - self.log, - "Batch not expecting block"; - "batch_epoch" => batch_id, - "batch_state" => ?batch.state(), - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + batch_state = ?batch.state(), + %peer_id, + %request_id, + ?batch_state, + "Batch not expecting block" ); return Ok(KeepChain); } debug!( - self.log, - "Batch failed. RPC Error"; - "batch_epoch" => batch_id, - "batch_state" => ?batch.state(), - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + batch_state = ?batch.state(), + error = ?err, + %peer_id, + %request_id, + "Batch download error" ); if let Some(active_requests) = self.peers.get_mut(peer_id) { active_requests.remove(&batch_id); @@ -883,12 +919,11 @@ impl SyncingChain { self.retry_batch_download(network, batch_id) } else { debug!( - self.log, - "Batch not found"; - "batch_epoch" => batch_id, - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + %peer_id, + %request_id, + batch_state, + "Batch not found" ); // this could be an error for an old batch, removed when the chain advances Ok(KeepChain) @@ -896,6 +931,7 @@ impl SyncingChain { } /// Sends and registers the request of a batch awaiting download. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn retry_batch_download( &mut self, network: &mut SyncNetworkContext, @@ -932,6 +968,7 @@ impl SyncingChain { } /// Requests the batch assigned to the given id from a given peer. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn send_batch( &mut self, network: &mut SyncNetworkContext, @@ -958,9 +995,9 @@ impl SyncingChain { .map(|epoch| epoch == batch_id) .unwrap_or(false) { - debug!(self.log, "Requesting optimistic batch"; "epoch" => batch_id, &batch, "batch_state" => batch_state); + debug!(epoch = %batch_id, %batch, %batch_state, "Requesting optimistic batch"); } else { - debug!(self.log, "Requesting batch"; "epoch" => batch_id, &batch, "batch_state" => batch_state); + debug!(epoch = %batch_id, %batch, %batch_state, "Requesting batch"); } // register the batch for this peer return self @@ -979,8 +1016,7 @@ impl SyncingChain { } Err(e) => { // NOTE: under normal conditions this shouldn't happen but we handle it anyway - warn!(self.log, "Could not send batch request"; - "batch_id" => batch_id, "error" => ?e, &batch); + warn!(%batch_id, error = %e, %batch, "Could not send batch request"); // register the failed download and check if the batch can be retried batch.start_downloading_from_peer(peer, 1)?; // fake request_id is not relevant self.peers @@ -1005,6 +1041,7 @@ impl SyncingChain { } /// Returns true if this chain is currently syncing. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn is_syncing(&self) -> bool { match self.state { ChainSyncingState::Syncing => true, @@ -1014,6 +1051,7 @@ impl SyncingChain { /// Kickstarts the chain by sending for processing batches that are ready and requesting more /// batches if needed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn resume( &mut self, network: &mut SyncNetworkContext, @@ -1026,6 +1064,7 @@ impl SyncingChain { /// Attempts to request the next required batches from the peer pool if the chain is syncing. It will exhaust the peer /// pool and left over batches until the batch buffer is reached or all peers are exhausted. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn request_batches(&mut self, network: &mut SyncNetworkContext) -> ProcessingResult { if !matches!(self.state, ChainSyncingState::Syncing) { return Ok(KeepChain); @@ -1052,10 +1091,7 @@ impl SyncingChain { // We wait for this batch before requesting any other batches. if let Some(epoch) = self.optimistic_start { if !self.good_peers_on_sampling_subnets(epoch, network) { - debug!( - self.log, - "Waiting for peers to be available on sampling column subnets" - ); + debug!("Waiting for peers to be available on sampling column subnets"); return Ok(KeepChain); } @@ -1114,6 +1150,7 @@ impl SyncingChain { /// Creates the next required batch from the chain. If there are no more batches required, /// `false` is returned. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn include_next_batch(&mut self, network: &mut SyncNetworkContext) -> Option { // don't request batches beyond the target head slot if self @@ -1147,10 +1184,7 @@ impl SyncingChain { // block and data column requests are currently coupled. This can be removed once we find a // way to decouple the requests and do retries individually, see issue #6258. if !self.good_peers_on_sampling_subnets(self.to_be_downloaded, network) { - debug!( - self.log, - "Waiting for peers to be available on custody column subnets" - ); + debug!("Waiting for peers to be available on custody column subnets"); return None; } @@ -1177,6 +1211,7 @@ impl SyncingChain { /// This produces a string of the form: [D,E,E,E,E] /// to indicate the current buffer state of the chain. The symbols are defined on each of the /// batch states. See [BatchState::visualize] for symbol definitions. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn visualize_batch_state(&self) -> String { let mut visualization_string = String::with_capacity((BATCH_BUFFER_SIZE * 3) as usize); @@ -1212,45 +1247,6 @@ impl SyncingChain { } } -impl slog::KV for &mut SyncingChain { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::KV::serialize(*self, record, serializer) - } -} - -impl slog::KV for SyncingChain { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - serializer.emit_u32("id", self.id)?; - Value::serialize(&self.start_epoch, record, "from", serializer)?; - Value::serialize( - &self.target_head_slot.epoch(T::EthSpec::slots_per_epoch()), - record, - "to", - serializer, - )?; - serializer.emit_arguments("end_root", &format_args!("{}", self.target_head_root))?; - Value::serialize( - &self.processing_target, - record, - "current_target", - serializer, - )?; - serializer.emit_usize("batches", self.batches.len())?; - serializer.emit_usize("peers", self.peers.len())?; - serializer.emit_arguments("state", &format_args!("{:?}", self.state))?; - slog::Result::Ok(()) - } -} - use super::batch::WrongState as WrongBatchState; impl From for RemoveChain { fn from(err: WrongBatchState) -> Self { diff --git a/beacon_node/network/src/sync/range_sync/chain_collection.rs b/beacon_node/network/src/sync/range_sync/chain_collection.rs index 4028530946..c6be3de576 100644 --- a/beacon_node/network/src/sync/range_sync/chain_collection.rs +++ b/beacon_node/network/src/sync/range_sync/chain_collection.rs @@ -12,11 +12,12 @@ use fnv::FnvHashMap; use lighthouse_network::service::api_types::Id; use lighthouse_network::PeerId; use lighthouse_network::SyncInfo; -use slog::{crit, debug, error}; +use logging::crit; use smallvec::SmallVec; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::Arc; +use tracing::{debug, error}; use types::EthSpec; use types::{Epoch, Hash256, Slot}; @@ -50,18 +51,15 @@ pub struct ChainCollection { head_chains: FnvHashMap>, /// The current sync state of the process. state: RangeSyncState, - /// Logger for the collection. - log: slog::Logger, } impl ChainCollection { - pub fn new(beacon_chain: Arc>, log: slog::Logger) -> Self { + pub fn new(beacon_chain: Arc>) -> Self { ChainCollection { beacon_chain, finalized_chains: FnvHashMap::default(), head_chains: FnvHashMap::default(), state: RangeSyncState::Idle, - log, } } @@ -295,9 +293,8 @@ impl ChainCollection { .expect("Chain exists"); match old_id { - Some(Some(old_id)) => debug!(self.log, "Switching finalized chains"; - "old_id" => old_id, &chain), - None => debug!(self.log, "Syncing new finalized chain"; &chain), + Some(Some(old_id)) => debug!(old_id, %chain, "Switching finalized chains"), + None => debug!(%chain, "Syncing new finalized chain"), Some(None) => { // this is the same chain. We try to advance it. } @@ -309,10 +306,10 @@ impl ChainCollection { if let Err(remove_reason) = chain.start_syncing(network, local_epoch, local_head_epoch) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed while switching chains"; "chain" => new_id, "reason" => ?remove_reason); + crit!(chain = new_id, reason = ?remove_reason, "Chain removed while switching chains"); } else { // this happens only if sending a batch over the `network` fails a lot - error!(self.log, "Chain removed while switching chains"; "chain" => new_id, "reason" => ?remove_reason); + error!(chain = new_id, reason = ?remove_reason, "Chain removed while switching chains"); } self.finalized_chains.remove(&new_id); self.on_chain_removed(&new_id, true, RangeSyncType::Finalized); @@ -330,7 +327,7 @@ impl ChainCollection { ) { // Include the awaiting head peers for (peer_id, peer_sync_info) in awaiting_head_peers.drain() { - debug!(self.log, "including head peer"); + debug!("including head peer"); self.add_peer_or_create_chain( local_epoch, peer_sync_info.head_root, @@ -362,16 +359,16 @@ impl ChainCollection { if syncing_chains.len() < PARALLEL_HEAD_CHAINS { // start this chain if it's not already syncing if !chain.is_syncing() { - debug!(self.log, "New head chain started syncing"; &chain); + debug!(%chain, "New head chain started syncing"); } if let Err(remove_reason) = chain.start_syncing(network, local_epoch, local_head_epoch) { self.head_chains.remove(&id); if remove_reason.is_critical() { - crit!(self.log, "Chain removed while switching head chains"; "chain" => id, "reason" => ?remove_reason); + crit!(chain = id, reason = ?remove_reason, "Chain removed while switching head chains"); } else { - error!(self.log, "Chain removed while switching head chains"; "chain" => id, "reason" => ?remove_reason); + error!(chain = id, reason = ?remove_reason, "Chain removed while switching head chains"); } } else { syncing_chains.push(id); @@ -407,7 +404,6 @@ impl ChainCollection { .start_slot(T::EthSpec::slots_per_epoch()); let beacon_chain = &self.beacon_chain; - let log_ref = &self.log; let is_outdated = |target_slot: &Slot, target_root: &Hash256| { target_slot <= &local_finalized_slot @@ -425,7 +421,7 @@ impl ChainCollection { if is_outdated(&chain.target_head_slot, &chain.target_head_root) || chain.available_peers() == 0 { - debug!(log_ref, "Purging out of finalized chain"; &chain); + debug!(%chain, "Purging out of finalized chain"); Some((*id, chain.is_syncing(), RangeSyncType::Finalized)) } else { None @@ -436,7 +432,7 @@ impl ChainCollection { if is_outdated(&chain.target_head_slot, &chain.target_head_root) || chain.available_peers() == 0 { - debug!(log_ref, "Purging out of date head chain"; &chain); + debug!(%chain, "Purging out of date head chain"); Some((*id, chain.is_syncing(), RangeSyncType::Head)) } else { None @@ -477,14 +473,14 @@ impl ChainCollection { .find(|(_, chain)| chain.has_same_target(target_head_slot, target_head_root)) { Some((&id, chain)) => { - debug!(self.log, "Adding peer to known chain"; "peer_id" => %peer, "sync_type" => ?sync_type, "id" => id); + debug!(peer_id = %peer, ?sync_type, id, "Adding peer to known chain"); debug_assert_eq!(chain.target_head_root, target_head_root); debug_assert_eq!(chain.target_head_slot, target_head_slot); if let Err(remove_reason) = chain.add_peer(network, peer) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed after adding peer"; "chain" => id, "reason" => ?remove_reason); + crit!(chain = %id, reason = ?remove_reason, "Chain removed after adding peer"); } else { - error!(self.log, "Chain removed after adding peer"; "chain" => id, "reason" => ?remove_reason); + error!(chain = %id, reason = ?remove_reason, "Chain removed after adding peer"); } let is_syncing = chain.is_syncing(); collection.remove(&id); @@ -501,9 +497,9 @@ impl ChainCollection { target_head_root, peer, sync_type.into(), - &self.log, ); - debug!(self.log, "New chain added to sync"; "peer_id" => peer_rpr, "sync_type" => ?sync_type, &new_chain); + + debug!(peer_id = peer_rpr, ?sync_type, %new_chain, "New chain added to sync"); collection.insert(id, new_chain); metrics::inc_counter_vec(&metrics::SYNCING_CHAINS_ADDED, &[sync_type.as_str()]); self.update_metrics(); diff --git a/beacon_node/network/src/sync/range_sync/range.rs b/beacon_node/network/src/sync/range_sync/range.rs index 38b032136c..ab9a88e4ac 100644 --- a/beacon_node/network/src/sync/range_sync/range.rs +++ b/beacon_node/network/src/sync/range_sync/range.rs @@ -44,17 +44,18 @@ use super::chain_collection::{ChainCollection, SyncChainStatus}; use super::sync_type::RangeSyncType; use crate::metrics; use crate::status::ToStatusMessage; -use crate::sync::network_context::SyncNetworkContext; +use crate::sync::network_context::{RpcResponseError, SyncNetworkContext}; use crate::sync::BatchProcessResult; use beacon_chain::block_verification_types::RpcBlock; use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::rpc::GoodbyeReason; use lighthouse_network::service::api_types::Id; use lighthouse_network::{PeerId, SyncInfo}; +use logging::crit; use lru_cache::LRUTimeCache; -use slog::{crit, debug, trace, warn}; use std::collections::HashMap; use std::sync::Arc; +use tracing::{debug, instrument, trace, warn}; use types::{Epoch, EthSpec, Hash256}; /// For how long we store failed finalized chains to prevent retries. @@ -74,23 +75,26 @@ pub struct RangeSync { chains: ChainCollection, /// Chains that have failed and are stored to prevent being retried. failed_chains: LRUTimeCache, - /// The syncing logger. - log: slog::Logger, } impl RangeSync where T: BeaconChainTypes, { - pub fn new(beacon_chain: Arc>, log: slog::Logger) -> Self { + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] + pub fn new(beacon_chain: Arc>) -> Self { RangeSync { beacon_chain: beacon_chain.clone(), - chains: ChainCollection::new(beacon_chain, log.clone()), + chains: ChainCollection::new(beacon_chain), failed_chains: LRUTimeCache::new(std::time::Duration::from_secs( FAILED_CHAINS_EXPIRY_SECONDS, )), awaiting_head_peers: HashMap::new(), - log, } } @@ -99,6 +103,12 @@ where self.failed_chains.keys().copied().collect() } + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn state(&self) -> SyncChainStatus { self.chains.state() } @@ -108,6 +118,12 @@ where /// may need to be synced as a result. A new peer, may increase the peer pool of a finalized /// chain, this may result in a different finalized chain from syncing as finalized chains are /// prioritised by peer-pool size. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn add_peer( &mut self, network: &mut SyncNetworkContext, @@ -133,14 +149,13 @@ where RangeSyncType::Finalized => { // Make sure we have not recently tried this chain if self.failed_chains.contains(&remote_info.finalized_root) { - debug!(self.log, "Disconnecting peer that belongs to previously failed chain"; - "failed_root" => %remote_info.finalized_root, "peer_id" => %peer_id); + debug!(failed_root = ?remote_info.finalized_root, %peer_id,"Disconnecting peer that belongs to previously failed chain"); network.goodbye_peer(peer_id, GoodbyeReason::IrrelevantNetwork); return; } // Finalized chain search - debug!(self.log, "Finalization sync peer joined"; "peer_id" => %peer_id); + debug!(%peer_id, "Finalization sync peer joined"); self.awaiting_head_peers.remove(&peer_id); // Because of our change in finalized sync batch size from 2 to 1 and our transition @@ -171,8 +186,7 @@ where if self.chains.is_finalizing_sync() { // If there are finalized chains to sync, finish these first, before syncing head // chains. - trace!(self.log, "Waiting for finalized sync to complete"; - "peer_id" => %peer_id, "awaiting_head_peers" => &self.awaiting_head_peers.len()); + trace!(%peer_id, awaiting_head_peers = &self.awaiting_head_peers.len(),"Waiting for finalized sync to complete"); self.awaiting_head_peers.insert(peer_id, remote_info); return; } @@ -204,6 +218,12 @@ where /// /// This function finds the chain that made this request. Once found, processes the result. /// This request could complete a chain or simply add to its progress. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn blocks_by_range_response( &mut self, network: &mut SyncNetworkContext, @@ -229,11 +249,17 @@ where } } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn handle_block_process_result( &mut self, network: &mut SyncNetworkContext, @@ -259,13 +285,19 @@ where } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } /// A peer has disconnected. This removes the peer from any ongoing chains and mappings. A /// disconnected peer could remove a chain + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn peer_disconnect(&mut self, network: &mut SyncNetworkContext, peer_id: &PeerId) { // if the peer is in the awaiting head mapping, remove it self.awaiting_head_peers.remove(peer_id); @@ -278,6 +310,12 @@ where /// which pool the peer is in. The chain may also have a batch or batches awaiting /// for this peer. If so we mark the batch as failed. The batch may then hit it's maximum /// retries. In this case, we need to remove the chain. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] fn remove_peer(&mut self, network: &mut SyncNetworkContext, peer_id: &PeerId) { for (removed_chain, sync_type, remove_reason) in self .chains @@ -297,6 +335,12 @@ where /// /// Check to see if the request corresponds to a pending batch. If so, re-request it if possible, if there have /// been too many failed attempts for the batch, remove the chain. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn inject_error( &mut self, network: &mut SyncNetworkContext, @@ -304,10 +348,11 @@ where batch_id: BatchId, chain_id: ChainId, request_id: Id, + err: RpcResponseError, ) { // check that this request is pending match self.chains.call_by_id(chain_id, |chain| { - chain.inject_error(network, batch_id, &peer_id, request_id) + chain.inject_error(network, batch_id, &peer_id, request_id, err) }) { Ok((removed_chain, sync_type)) => { if let Some((removed_chain, remove_reason)) = removed_chain { @@ -321,11 +366,17 @@ where } } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] fn on_chain_removed( &mut self, chain: SyncingChain, @@ -335,14 +386,18 @@ where op: &'static str, ) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed"; "sync_type" => ?sync_type, &chain, "reason" => ?remove_reason, "op" => op); + crit!(?sync_type, %chain, reason = ?remove_reason,op, "Chain removed"); } else { - debug!(self.log, "Chain removed"; "sync_type" => ?sync_type, &chain, "reason" => ?remove_reason, "op" => op); + debug!(?sync_type, %chain, reason = ?remove_reason,op, "Chain removed"); } if let RemoveChain::ChainFailed { blacklist, .. } = remove_reason { if RangeSyncType::Finalized == sync_type && blacklist { - warn!(self.log, "Chain failed! Syncing to its head won't be retried for at least the next {} seconds", FAILED_CHAINS_EXPIRY_SECONDS; &chain); + warn!( + %chain, + "Chain failed! Syncing to its head won't be retried for at least the next {} seconds", + FAILED_CHAINS_EXPIRY_SECONDS + ); self.failed_chains.insert(chain.target_head_root); } } @@ -369,6 +424,12 @@ where } /// Kickstarts sync. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn resume(&mut self, network: &mut SyncNetworkContext) { for (removed_chain, sync_type, remove_reason) in self.chains.call_all(|chain| chain.resume(network)) diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index 10117285eb..fe72979930 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -19,8 +19,8 @@ use beacon_chain::{ block_verification_types::{AsBlock, BlockImportData}, data_availability_checker::Availability, test_utils::{ - build_log, generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, - BeaconChainHarness, EphemeralHarnessType, LoggerType, NumBlobs, + generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, + BeaconChainHarness, EphemeralHarnessType, NumBlobs, }, validator_monitor::timestamp_now, AvailabilityPendingExecutedBlock, AvailabilityProcessingStatus, BlockError, @@ -37,9 +37,9 @@ use lighthouse_network::{ types::SyncState, NetworkConfig, NetworkGlobals, PeerId, }; -use slog::info; use slot_clock::{SlotClock, TestingSlotClock}; use tokio::sync::mpsc; +use tracing::info; use types::{ data_column_sidecar::ColumnIndex, test_utils::{SeedableRng, TestRandom, XorShiftRng}, @@ -55,22 +55,12 @@ type DCByRootId = (SyncRequestId, Vec); impl TestRig { pub fn test_setup() -> Self { - let logger_type = if cfg!(feature = "test_logger") { - LoggerType::Test - } else if cfg!(feature = "ci_logger") { - LoggerType::CI - } else { - LoggerType::Null - }; - let log = build_log(slog::Level::Trace, logger_type); - // Use `fork_from_env` logic to set correct fork epochs let spec = test_spec::(); // Initialise a new beacon chain let harness = BeaconChainHarness::>::builder(E) .spec(Arc::new(spec)) - .logger(log.clone()) .deterministic_keypairs(1) .fresh_ephemeral_store() .mock_execution_layer() @@ -95,7 +85,6 @@ impl TestRig { let network_config = Arc::new(NetworkConfig::default()); let globals = Arc::new(NetworkGlobals::new_test_globals( Vec::new(), - &log, network_config, chain.spec.clone(), )); @@ -104,7 +93,6 @@ impl TestRig { sync_tx, chain.clone(), harness.runtime.task_executor.clone(), - log.clone(), ); let fork_name = chain.spec.fork_name_at_slot::(chain.slot().unwrap()); @@ -137,11 +125,9 @@ impl TestRig { required_successes: vec![SAMPLING_REQUIRED_SUCCESSES], }, fork_context, - log.clone(), ), harness, fork_name, - log, spec, } } @@ -165,7 +151,7 @@ impl TestRig { } pub fn log(&self, msg: &str) { - info!(self.log, "TEST_RIG"; "msg" => msg); + info!(msg, "TEST_RIG"); } pub fn after_deneb(&self) -> bool { @@ -1217,8 +1203,12 @@ impl TestRig { payload_verification_status: PayloadVerificationStatus::Verified, is_valid_merge_transition_block: false, }; - let executed_block = - AvailabilityPendingExecutedBlock::new(block, import_data, payload_verification_outcome); + let executed_block = AvailabilityPendingExecutedBlock::new( + block, + import_data, + payload_verification_outcome, + self.network_globals.custody_columns_count() as usize, + ); match self .harness .chain @@ -2318,11 +2308,6 @@ mod deneb_only { }) } - fn log(self, msg: &str) -> Self { - self.rig.log(msg); - self - } - fn trigger_unknown_block_from_attestation(mut self) -> Self { let block_root = self.block.canonical_root(); self.rig @@ -2626,6 +2611,11 @@ mod deneb_only { .block_imported() } + fn log(self, msg: &str) -> Self { + self.rig.log(msg); + self + } + fn parent_block_then_empty_parent_blobs(self) -> Self { self.log( " Return empty blobs for parent, block errors with missing components, downscore", diff --git a/beacon_node/network/src/sync/tests/mod.rs b/beacon_node/network/src/sync/tests/mod.rs index ef2bec80b8..ec24ddb036 100644 --- a/beacon_node/network/src/sync/tests/mod.rs +++ b/beacon_node/network/src/sync/tests/mod.rs @@ -8,7 +8,6 @@ use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType}; use beacon_processor::WorkEvent; use lighthouse_network::NetworkGlobals; use rand_chacha::ChaCha20Rng; -use slog::Logger; use slot_clock::ManualSlotClock; use std::sync::Arc; use store::MemoryStore; @@ -64,6 +63,5 @@ struct TestRig { /// `rng` for generating test blocks and blobs. rng: ChaCha20Rng, fork_name: ForkName, - log: Logger, spec: Arc, } diff --git a/beacon_node/network/src/sync/tests/range.rs b/beacon_node/network/src/sync/tests/range.rs index ddd4626cce..ca4344c0b2 100644 --- a/beacon_node/network/src/sync/tests/range.rs +++ b/beacon_node/network/src/sync/tests/range.rs @@ -449,7 +449,15 @@ fn build_rpc_block( RpcBlock::new(None, block, Some(blobs.clone())).unwrap() } Some(DataSidecars::DataColumns(columns)) => { - RpcBlock::new_with_custody_columns(None, block, columns.clone(), spec).unwrap() + RpcBlock::new_with_custody_columns( + None, + block, + columns.clone(), + // TODO(das): Assumes CGC = max value. Change if we want to do more complex tests + columns.len(), + spec, + ) + .unwrap() } None => RpcBlock::new_without_blobs(None, block), } diff --git a/beacon_node/operation_pool/src/bls_to_execution_changes.rs b/beacon_node/operation_pool/src/bls_to_execution_changes.rs index cbab97e719..b36299b51a 100644 --- a/beacon_node/operation_pool/src/bls_to_execution_changes.rs +++ b/beacon_node/operation_pool/src/bls_to_execution_changes.rs @@ -112,7 +112,7 @@ impl BlsToExecutionChanges { head_state .validators() .get(validator_index as usize) - .map_or(true, |validator| { + .is_none_or(|validator| { let prune = validator.has_execution_withdrawal_credential(spec) && head_block .message() diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 835133a059..584a5f9f32 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -767,7 +767,7 @@ fn prune_validator_hash_map( && head_state .validators() .get(validator_index as usize) - .map_or(true, |validator| !prune_if(validator_index, validator)) + .is_none_or(|validator| !prune_if(validator_index, validator)) }); } diff --git a/beacon_node/operation_pool/src/reward_cache.rs b/beacon_node/operation_pool/src/reward_cache.rs index dd9902353f..adedcb5e39 100644 --- a/beacon_node/operation_pool/src/reward_cache.rs +++ b/beacon_node/operation_pool/src/reward_cache.rs @@ -83,7 +83,7 @@ impl RewardCache { if self .initialization .as_ref() - .map_or(true, |init| *init != new_init) + .is_none_or(|init| *init != new_init) { self.update_previous_epoch_participation(state) .map_err(OpPoolError::RewardCacheUpdatePrevEpoch)?; diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 4c2daecdd3..7d086dcc32 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -776,6 +776,15 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("state-cache-headroom") + .long("state-cache-headroom") + .value_name("N") + .help("Minimum number of states to cull from the state cache when it gets full") + .default_value("1") + .action(ArgAction::Set) + .display_order(0) + ) .arg( Arg::new("block-cache-size") .long("block-cache-size") @@ -812,7 +821,7 @@ pub fn cli_app() -> Command { .long("state-cache-size") .value_name("STATE_CACHE_SIZE") .help("Specifies the size of the state cache") - .default_value("128") + .default_value("32") .action(ArgAction::Set) .display_order(0) ) @@ -1009,7 +1018,7 @@ pub fn cli_app() -> Command { database when they are older than the data availability boundary \ relative to the current epoch.") .action(ArgAction::Set) - .default_value("1") + .default_value("256") .display_order(0) ) .arg( @@ -1460,6 +1469,15 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("builder-disable-ssz") + .long("builder-disable-ssz") + .value_name("BOOLEAN") + .help("Disables sending requests using SSZ over the builder API.") + .requires("builder") + .action(ArgAction::SetTrue) + .display_order(0) + ) .arg( Arg::new("reset-payload-statuses") .long("reset-payload-statuses") @@ -1509,6 +1527,19 @@ pub fn cli_app() -> Command { .help_heading(FLAG_HEADER) .display_order(0) ) + .arg( + Arg::new("sync-tolerance-epochs") + .long("sync-tolerance-epochs") + .help("Overrides the default SYNC_TOLERANCE_EPOCHS. This flag is not intended \ + for production and MUST only be used in TESTING only. This is primarily used \ + for testing range sync, to prevent the node from producing a block before the \ + node is synced with the network which may result in the node getting \ + disconnected from peers immediately.") + .hide(true) + .requires("enable_http") + .action(ArgAction::Set) + .display_order(0) + ) .arg( Arg::new("gui") .long("gui") @@ -1599,5 +1630,38 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("delay-block-publishing") + .long("delay-block-publishing") + .value_name("SECONDS") + .action(ArgAction::Set) + .help_heading(FLAG_HEADER) + .help("TESTING ONLY: Artificially delay block publishing by the specified number of seconds. \ + This only works for if `BroadcastValidation::Gossip` is used (default). \ + DO NOT USE IN PRODUCTION.") + .hide(true) + .display_order(0) + ) + .arg( + Arg::new("delay-data-column-publishing") + .long("delay-data-column-publishing") + .value_name("SECONDS") + .action(ArgAction::Set) + .help_heading(FLAG_HEADER) + .help("TESTING ONLY: Artificially delay data column publishing by the specified number of seconds. \ + Limitation: If `delay-block-publishing` is also used, data columns will be delayed for a \ + minimum of `delay-block-publishing` seconds. + DO NOT USE IN PRODUCTION.") + .hide(true) + .display_order(0) + ) + .arg( + Arg::new("invalid-block-roots") + .long("invalid-block-roots") + .value_name("FILE") + .help("Path to a comma separated file containing block roots that should be treated as invalid during block verification.") + .action(ArgAction::Set) + .hide(true) + ) .group(ArgGroup::new("enable_http").args(["http", "gui", "staking"]).multiple(true)) } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 24d569bea2..8723c2d708 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -2,7 +2,7 @@ use account_utils::{read_input_from_user, STDIN_INPUTS_FLAG}; use beacon_chain::chain_config::{ DisallowedReOrgOffsets, ReOrgThreshold, DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR, DEFAULT_RE_ORG_HEAD_THRESHOLD, DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, - DEFAULT_RE_ORG_PARENT_THRESHOLD, + DEFAULT_RE_ORG_PARENT_THRESHOLD, INVALID_HOLESKY_BLOCK_ROOT, }; use beacon_chain::graffiti_calculator::GraffitiOrigin; use beacon_chain::TrustedSetup; @@ -18,17 +18,18 @@ use http_api::TlsConfig; use lighthouse_network::ListenAddress; use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, PeerIdSerialized}; use sensitive_url::SensitiveUrl; -use slog::{info, warn, Logger}; use std::cmp::max; +use std::collections::HashSet; use std::fmt::Debug; use std::fs; -use std::io::IsTerminal; +use std::io::{IsTerminal, Read}; use std::net::Ipv6Addr; use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs}; use std::num::NonZeroU16; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; +use tracing::{error, info, warn}; use types::graffiti::GraffitiString; use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes}; @@ -46,7 +47,6 @@ pub fn get_config( context: &RuntimeContext, ) -> Result { let spec = &context.eth2_config.spec; - let log = context.log(); let mut client_config = ClientConfig::default(); @@ -64,12 +64,10 @@ pub fn get_config( let stdin_inputs = cfg!(windows) || cli_args.get_flag(STDIN_INPUTS_FLAG); if std::io::stdin().is_terminal() || stdin_inputs { info!( - log, "You are about to delete the chain database. This is irreversable \ and you will need to resync the chain." ); info!( - log, "Type 'confirm' to delete the database. Any other input will leave \ the database intact and Lighthouse will exit." ); @@ -80,14 +78,13 @@ pub fn get_config( let freezer_db = client_config.get_freezer_db_path(); let blobs_db = client_config.get_blobs_db_path(); purge_db(chain_db, freezer_db, blobs_db)?; - info!(log, "Database was deleted."); + info!("Database was deleted."); } else { - info!(log, "Database was not deleted. Lighthouse will now close."); + info!("Database was not deleted. Lighthouse will now close."); std::process::exit(1); } } else { warn!( - log, "The `--purge-db` flag was passed, but Lighthouse is not running \ interactively. The database was not purged. Use `--purge-db-force` \ to purge the database without requiring confirmation." @@ -104,7 +101,7 @@ pub fn get_config( let mut log_dir = client_config.data_dir().clone(); // remove /beacon from the end log_dir.pop(); - info!(log, "Data directory initialised"; "datadir" => log_dir.into_os_string().into_string().expect("Datadir should be a valid os string")); + info!(datadir = %log_dir.into_os_string().into_string().expect("Datadir should be a valid os string"), "Data directory initialised"); /* * Networking @@ -112,7 +109,7 @@ pub fn get_config( let data_dir_ref = client_config.data_dir().clone(); - set_network_config(&mut client_config.network, cli_args, &data_dir_ref, log)?; + set_network_config(&mut client_config.network, cli_args, &data_dir_ref)?; /* * Staking flag @@ -174,14 +171,10 @@ pub fn get_config( client_config.http_api.duplicate_block_status_code = parse_required(cli_args, "http-duplicate-block-status")?; - - client_config.http_api.enable_light_client_server = - !cli_args.get_flag("disable-light-client-server"); } if cli_args.get_flag("light-client-server") { warn!( - log, "The --light-client-server flag is deprecated. The light client server is enabled \ by default" ); @@ -191,6 +184,12 @@ pub fn get_config( client_config.chain.enable_light_client_server = false; } + if let Some(sync_tolerance_epochs) = + clap_utils::parse_optional(cli_args, "sync-tolerance-epochs")? + { + client_config.chain.sync_tolerance_epochs = sync_tolerance_epochs; + } + if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { client_config.chain.shuffling_cache_size = cache_size; } @@ -256,8 +255,8 @@ pub fn get_config( // (e.g. using the --staking flag). if cli_args.get_flag("staking") { warn!( - log, - "Running HTTP server on port {}", client_config.http_api.listen_port + "Running HTTP server on port {}", + client_config.http_api.listen_port ); } @@ -271,11 +270,11 @@ pub fn get_config( */ if cli_args.get_flag("dummy-eth1") { - warn!(log, "The --dummy-eth1 flag is deprecated"); + warn!("The --dummy-eth1 flag is deprecated"); } if cli_args.get_flag("eth1") { - warn!(log, "The --eth1 flag is deprecated"); + warn!("The --eth1 flag is deprecated"); } if let Some(val) = cli_args.get_one::("eth1-blocks-per-log-query") { @@ -303,18 +302,14 @@ pub fn get_config( endpoints.as_str(), SensitiveUrl::parse, "--execution-endpoint", - log, )?; // JWTs are required if `--execution-endpoint` is supplied. They can be either passed via // file_path or directly as string. - let secret_file: PathBuf; // Parse a single JWT secret from a given file_path, logging warnings if multiple are supplied. if let Some(secret_files) = cli_args.get_one::("execution-jwt") { - secret_file = - parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt", log)?; - + secret_file = parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt")?; // Check if the JWT secret key is passed directly via cli flag and persist it to the default // file location. } else if let Some(jwt_secret_key) = cli_args.get_one::("execution-jwt-secret-key") { @@ -337,8 +332,7 @@ pub fn get_config( // Parse and set the payload builder, if any. if let Some(endpoint) = cli_args.get_one::("builder") { - let payload_builder = - parse_only_one_value(endpoint, SensitiveUrl::parse, "--builder", log)?; + let payload_builder = parse_only_one_value(endpoint, SensitiveUrl::parse, "--builder")?; el_config.builder_url = Some(payload_builder); el_config.builder_user_agent = clap_utils::parse_optional(cli_args, "builder-user-agent")?; @@ -346,6 +340,8 @@ pub fn get_config( el_config.builder_header_timeout = clap_utils::parse_optional(cli_args, "builder-header-timeout")? .map(Duration::from_millis); + + el_config.disable_builder_ssz_requests = cli_args.get_flag("builder-disable-ssz"); } // Set config values from parse values. @@ -437,7 +433,7 @@ pub fn get_config( } if clap_utils::parse_optional::(cli_args, "slots-per-restore-point")?.is_some() { - warn!(log, "The slots-per-restore-point flag is deprecated"); + warn!("The slots-per-restore-point flag is deprecated"); } if let Some(backend) = clap_utils::parse_optional(cli_args, "beacon-node-backend")? { @@ -454,6 +450,12 @@ pub fn get_config( client_config.chain.epochs_per_migration = epochs_per_migration; } + if let Some(state_cache_headroom) = + clap_utils::parse_optional(cli_args, "state-cache-headroom")? + { + client_config.store.state_cache_headroom = state_cache_headroom; + } + if let Some(prune_blobs) = clap_utils::parse_optional(cli_args, "prune-blobs")? { client_config.store.prune_blobs = prune_blobs; } @@ -510,10 +512,9 @@ pub fn get_config( client_config.eth1.set_block_cache_truncation::(spec); info!( - log, - "Deposit contract"; - "deploy_block" => client_config.eth1.deposit_contract_deploy_block, - "address" => &client_config.eth1.deposit_contract_address + deploy_block = client_config.eth1.deposit_contract_deploy_block, + address = &client_config.eth1.deposit_contract_address, + "Deposit contract" ); // Only append network config bootnodes if discovery is not disabled @@ -895,14 +896,48 @@ pub fn get_config( .max_gossip_aggregate_batch_size = clap_utils::parse_required(cli_args, "beacon-processor-aggregate-batch-size")?; + if let Some(delay) = clap_utils::parse_optional(cli_args, "delay-block-publishing")? { + client_config.chain.block_publishing_delay = Some(Duration::from_secs_f64(delay)); + } + + if let Some(delay) = clap_utils::parse_optional(cli_args, "delay-data-column-publishing")? { + client_config.chain.data_column_publishing_delay = Some(Duration::from_secs_f64(delay)); + } + + if let Some(invalid_block_roots_file_path) = + clap_utils::parse_optional::(cli_args, "invalid-block-roots")? + { + let mut file = std::fs::File::open(invalid_block_roots_file_path) + .map_err(|e| format!("Failed to open invalid-block-roots file: {}", e))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|e| format!("Failed to read invalid-block-roots file {}", e))?; + let invalid_block_roots: HashSet = contents + .split(',') + .filter_map( + |s| match Hash256::from_str(s.strip_prefix("0x").unwrap_or(s).trim()) { + Ok(block_root) => Some(block_root), + Err(error) => { + warn!(block_root = s, ?error, "Unable to parse invalid block root",); + None + } + }, + ) + .collect(); + client_config.chain.invalid_block_roots = invalid_block_roots; + } else if spec + .config_name + .as_ref() + .is_some_and(|network_name| network_name == "holesky") + { + client_config.chain.invalid_block_roots = HashSet::from([*INVALID_HOLESKY_BLOCK_ROOT]); + } + Ok(client_config) } /// Gets the listening_addresses for lighthouse based on the cli options. -pub fn parse_listening_addresses( - cli_args: &ArgMatches, - log: &Logger, -) -> Result { +pub fn parse_listening_addresses(cli_args: &ArgMatches) -> Result { let listen_addresses_str = cli_args .get_many::("listen-address") .unwrap_or_default(); @@ -1005,7 +1040,7 @@ pub fn parse_listening_addresses( (None, Some(ipv6)) => { // A single ipv6 address was provided. Set the ports if cli_args.value_source("port6") == Some(ValueSource::CommandLine) { - warn!(log, "When listening only over IPv6, use the --port flag. The value of --port6 will be ignored."); + warn!("When listening only over IPv6, use the --port flag. The value of --port6 will be ignored."); } // If we are only listening on ipv6 and the user has specified --port6, lets just use @@ -1019,11 +1054,11 @@ pub fn parse_listening_addresses( .unwrap_or(port); if maybe_disc6_port.is_some() { - warn!(log, "When listening only over IPv6, use the --discovery-port flag. The value of --discovery-port6 will be ignored.") + warn!("When listening only over IPv6, use the --discovery-port flag. The value of --discovery-port6 will be ignored.") } if maybe_quic6_port.is_some() { - warn!(log, "When listening only over IPv6, use the --quic-port flag. The value of --quic-port6 will be ignored.") + warn!("When listening only over IPv6, use the --quic-port flag. The value of --quic-port6 will be ignored.") } // use zero ports if required. If not, use the specific udp port. If none given, use @@ -1145,7 +1180,6 @@ pub fn set_network_config( config: &mut NetworkConfig, cli_args: &ArgMatches, data_dir: &Path, - log: &Logger, ) -> Result<(), String> { // If a network dir has been specified, override the `datadir` definition. if let Some(dir) = cli_args.get_one::("network-dir") { @@ -1170,7 +1204,7 @@ pub fn set_network_config( config.shutdown_after_sync = true; } - config.set_listening_addr(parse_listening_addresses(cli_args, log)?); + config.set_listening_addr(parse_listening_addresses(cli_args)?); // A custom target-peers command will overwrite the --proposer-only default. if let Some(target_peers_str) = cli_args.get_one::("target-peers") { @@ -1198,10 +1232,10 @@ pub fn set_network_config( .parse() .map_err(|_| format!("Not valid as ENR nor Multiaddr: {}", addr))?; if !multi.iter().any(|proto| matches!(proto, Protocol::Udp(_))) { - slog::error!(log, "Missing UDP in Multiaddr {}", multi.to_string()); + error!(multiaddr = multi.to_string(), "Missing UDP in Multiaddr"); } if !multi.iter().any(|proto| matches!(proto, Protocol::P2p(_))) { - slog::error!(log, "Missing P2P in Multiaddr {}", multi.to_string()); + error!(multiaddr = multi.to_string(), "Missing P2P in Multiaddr"); } multiaddrs.push(multi); } @@ -1236,7 +1270,7 @@ pub fn set_network_config( }) .collect::, _>>()?; if config.trusted_peers.len() >= config.target_peers { - slog::warn!(log, "More trusted peers than the target peer limit. This will prevent efficient peer selection criteria."; "target_peers" => config.target_peers, "trusted_peers" => config.trusted_peers.len()); + warn!( target_peers = config.target_peers, trusted_peers = config.trusted_peers.len(),"More trusted peers than the target peer limit. This will prevent efficient peer selection criteria."); } } @@ -1336,14 +1370,14 @@ pub fn set_network_config( match addr.parse::() { Ok(IpAddr::V4(v4_addr)) => { if let Some(used) = enr_ip4.as_ref() { - warn!(log, "More than one Ipv4 ENR address provided"; "used" => %used, "ignored" => %v4_addr) + warn!(used = %used, ignored = %v4_addr, "More than one Ipv4 ENR address provided") } else { enr_ip4 = Some(v4_addr) } } Ok(IpAddr::V6(v6_addr)) => { if let Some(used) = enr_ip6.as_ref() { - warn!(log, "More than one Ipv6 ENR address provided"; "used" => %used, "ignored" => %v6_addr) + warn!(used = %used, ignored = %v6_addr,"More than one Ipv6 ENR address provided") } else { enr_ip6 = Some(v6_addr) } @@ -1409,13 +1443,13 @@ pub fn set_network_config( } if parse_flag(cli_args, "disable-packet-filter") { - warn!(log, "Discv5 packet filter is disabled"); + warn!("Discv5 packet filter is disabled"); config.discv5_config.enable_packet_filter = false; } if parse_flag(cli_args, "disable-discovery") { config.disable_discovery = true; - warn!(log, "Discovery is disabled. New peers will not be found"); + warn!("Discovery is disabled. New peers will not be found"); } if parse_flag(cli_args, "disable-quic") { @@ -1462,7 +1496,10 @@ pub fn set_network_config( config.target_peers = 15; } config.proposer_only = true; - warn!(log, "Proposer-only mode enabled"; "info"=> "Do not connect a validator client to this node unless via the --proposer-nodes flag"); + warn!( + info = "Proposer-only mode enabled", + "Do not connect a validator client to this node unless via the --proposer-nodes flag" + ); } // The inbound rate limiter is enabled by default unless `disabled` via the // `disable-inbound-rate-limiter` flag. @@ -1520,7 +1557,6 @@ pub fn parse_only_one_value( cli_value: &str, parser: F, flag_name: &str, - log: &Logger, ) -> Result where F: Fn(&str) -> Result, @@ -1534,11 +1570,10 @@ where if values.len() > 1 { warn!( - log, - "Multiple values provided"; - "info" => "multiple values are deprecated, only the first value will be used", - "count" => values.len(), - "flag" => flag_name + info = "Multiple values provided", + count = values.len(), + flag = flag_name, + "multiple values are deprecated, only the first value will be used" ); } diff --git a/beacon_node/src/lib.rs b/beacon_node/src/lib.rs index e3802c837c..a7f92434ce 100644 --- a/beacon_node/src/lib.rs +++ b/beacon_node/src/lib.rs @@ -12,10 +12,10 @@ pub use config::{get_config, get_data_dir, set_network_config}; use environment::RuntimeContext; pub use eth2_config::Eth2Config; use slasher::{DatabaseBackendOverride, Slasher}; -use slog::{info, warn}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use store::database::interface::BeaconNodeBackend; +use tracing::{info, warn}; use types::{ChainSpec, Epoch, EthSpec, ForkName}; /// A type-alias to the tighten the definition of a production-intended `Client`. @@ -63,7 +63,6 @@ impl ProductionBeaconNode { let spec = context.eth2_config().spec.clone(); let client_genesis = client_config.genesis.clone(); let store_config = client_config.store.clone(); - let log = context.log().clone(); let _datadir = client_config.create_data_dir()?; let db_path = client_config.create_db_path()?; let freezer_db_path = client_config.create_freezer_db_path()?; @@ -72,20 +71,18 @@ impl ProductionBeaconNode { if let Some(legacy_dir) = client_config.get_existing_legacy_data_dir() { warn!( - log, - "Legacy datadir location"; - "msg" => "this occurs when using relative paths for a datadir location", - "location" => ?legacy_dir, + msg = "this occurs when using relative paths for a datadir location", + location = ?legacy_dir, + "Legacy datadir location" ) } if let Err(misaligned_forks) = validator_fork_epochs(&spec) { warn!( - log, - "Fork boundaries are not well aligned / multiples of 256"; - "info" => "This may cause issues as fork boundaries do not align with the \ - start of sync committee period.", - "misaligned_forks" => ?misaligned_forks, + info = "This may cause issues as fork boundaries do not align with the \ + start of sync committee period.", + ?misaligned_forks, + "Fork boundaries are not well aligned / multiples of 256" ); } @@ -94,42 +91,30 @@ impl ProductionBeaconNode { .chain_spec(spec.clone()) .beacon_processor(client_config.beacon_processor.clone()) .http_api_config(client_config.http_api.clone()) - .disk_store( - &db_path, - &freezer_db_path, - &blobs_db_path, - store_config, - log.clone(), - )?; + .disk_store(&db_path, &freezer_db_path, &blobs_db_path, store_config)?; let builder = if let Some(mut slasher_config) = client_config.slasher.clone() { match slasher_config.override_backend() { DatabaseBackendOverride::Success(old_backend) => { info!( - log, - "Slasher backend overridden"; - "reason" => "database exists", - "configured_backend" => %old_backend, - "override_backend" => %slasher_config.backend, + reason = "database exists", + configured_backend = %old_backend, + override_backend = %slasher_config.backend, + "Slasher backend overridden" ); } DatabaseBackendOverride::Failure(path) => { warn!( - log, - "Slasher backend override failed"; - "advice" => "delete old MDBX database or enable MDBX backend", - "path" => path.display() + advice = "delete old MDBX database or enable MDBX backend", + path = %path.display(), + "Slasher backend override failed" ); } _ => {} } let slasher = Arc::new( - Slasher::open( - slasher_config, - spec, - log.new(slog::o!("service" => "slasher")), - ) - .map_err(|e| format!("Slasher open error: {:?}", e))?, + Slasher::open(slasher_config, spec) + .map_err(|e| format!("Slasher open error: {:?}", e))?, ); builder.slasher(slasher) } else { @@ -149,19 +134,17 @@ impl ProductionBeaconNode { .await?; let builder = if client_config.sync_eth1_chain { info!( - log, - "Block production enabled"; - "endpoint" => format!("{:?}", &client_config.eth1.endpoint), - "method" => "json rpc via http" + endpoint = ?client_config.eth1.endpoint, + method = "json rpc via http", + "Block production enabled" ); builder .caching_eth1_backend(client_config.eth1.clone()) .await? } else { info!( - log, - "Block production disabled"; - "reason" => "no eth1 backend configured" + reason = "no eth1 backend configured", + "Block production disabled" ); builder.no_eth1_backend()? }; diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index d2f3a5c562..d17a8f04d6 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -30,12 +30,12 @@ parking_lot = { workspace = true } redb = { version = "2.1.3", optional = true } safe_arith = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -sloggers = { workspace = true } smallvec = { workspace = true } state_processing = { workspace = true } strum = { workspace = true } superstruct = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } xdelta3 = { workspace = true } zstd = { workspace = true } diff --git a/beacon_node/store/src/chunked_iter.rs b/beacon_node/store/src/chunked_iter.rs index 8f6682e758..f2821286ec 100644 --- a/beacon_node/store/src/chunked_iter.rs +++ b/beacon_node/store/src/chunked_iter.rs @@ -1,6 +1,6 @@ use crate::chunked_vector::{chunk_key, Chunk, Field}; use crate::{HotColdDB, ItemStore}; -use slog::error; +use tracing::error; use types::{ChainSpec, EthSpec, Slot}; /// Iterator over the values of a `BeaconState` vector field (like `block_roots`). @@ -82,9 +82,8 @@ where .cloned() .or_else(|| { error!( - self.store.log, - "Missing chunk value in forwards iterator"; - "vector index" => vindex + vector_index = vindex, + "Missing chunk value in forwards iterator" ); None })?; @@ -100,19 +99,17 @@ where ) .map_err(|e| { error!( - self.store.log, - "Database error in forwards iterator"; - "chunk index" => self.next_cindex, - "error" => format!("{:?}", e) + chunk_index = self.next_cindex, + error = ?e, + "Database error in forwards iterator" ); e }) .ok()? .or_else(|| { error!( - self.store.log, - "Missing chunk in forwards iterator"; - "chunk index" => self.next_cindex + chunk_index = self.next_cindex, + "Missing chunk in forwards iterator" ); None })?; diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 64765fd66a..a84573eb40 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -21,6 +21,7 @@ pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192; pub const DEFAULT_EPOCHS_PER_STATE_DIFF: u64 = 8; pub const DEFAULT_BLOCK_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(64); pub const DEFAULT_STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(128); +pub const DEFAULT_STATE_CACHE_HEADROOM: NonZeroUsize = new_non_zero_usize(1); pub const DEFAULT_COMPRESSION_LEVEL: i32 = 1; pub const DEFAULT_HISTORIC_STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(1); pub const DEFAULT_HDIFF_BUFFER_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(16); @@ -35,6 +36,8 @@ pub struct StoreConfig { pub block_cache_size: NonZeroUsize, /// Maximum number of states to store in the in-memory state cache. pub state_cache_size: NonZeroUsize, + /// Minimum number of states to cull from the state cache upon fullness. + pub state_cache_headroom: NonZeroUsize, /// Compression level for blocks, state diffs and other compressed values. pub compression_level: i32, /// Maximum number of historic states to store in the in-memory historic state cache. @@ -107,6 +110,7 @@ impl Default for StoreConfig { Self { block_cache_size: DEFAULT_BLOCK_CACHE_SIZE, state_cache_size: DEFAULT_STATE_CACHE_SIZE, + state_cache_headroom: DEFAULT_STATE_CACHE_HEADROOM, historic_state_cache_size: DEFAULT_HISTORIC_STATE_CACHE_SIZE, hdiff_buffer_cache_size: DEFAULT_HDIFF_BUFFER_CACHE_SIZE, compression_level: DEFAULT_COMPRESSION_LEVEL, diff --git a/beacon_node/store/src/garbage_collection.rs b/beacon_node/store/src/garbage_collection.rs index 06393f2d21..586db44c89 100644 --- a/beacon_node/store/src/garbage_collection.rs +++ b/beacon_node/store/src/garbage_collection.rs @@ -2,7 +2,7 @@ use crate::database::interface::BeaconNodeBackend; use crate::hot_cold_store::HotColdDB; use crate::{DBColumn, Error}; -use slog::debug; +use tracing::debug; use types::EthSpec; impl HotColdDB, BeaconNodeBackend> @@ -24,11 +24,7 @@ where } }); if !ops.is_empty() { - debug!( - self.log, - "Garbage collecting {} temporary states", - ops.len() - ); + debug!("Garbage collecting {} temporary states", ops.len()); self.delete_batch(DBColumn::BeaconState, ops.clone())?; self.delete_batch(DBColumn::BeaconStateSummary, ops.clone())?; diff --git a/beacon_node/store/src/hdiff.rs b/beacon_node/store/src/hdiff.rs index a29e680eb5..a659c65452 100644 --- a/beacon_node/store/src/hdiff.rs +++ b/beacon_node/store/src/hdiff.rs @@ -21,8 +21,8 @@ static EMPTY_PUBKEY: LazyLock = LazyLock::new(PublicKeyBytes::em pub enum Error { InvalidHierarchy, DiffDeletionsNotSupported, - UnableToComputeDiff, - UnableToApplyDiff, + UnableToComputeDiff(xdelta3::Error), + UnableToApplyDiff(xdelta3::Error), BalancesIncompleteChunk, Compression(std::io::Error), InvalidSszState(ssz::DecodeError), @@ -323,9 +323,15 @@ impl BytesDiff { } pub fn compute_xdelta(source_bytes: &[u8], target_bytes: &[u8]) -> Result { - let bytes = xdelta3::encode(target_bytes, source_bytes) - .ok_or(Error::UnableToComputeDiff) - .unwrap(); + // TODO(hdiff): Use a smaller estimate for the output diff buffer size, currently the + // xdelta3 lib will use 2x the size of the source plus the target length, which is 4x the + // size of the hdiff buffer. In practice, diffs are almost always smaller than buffers (by a + // signficiant factor), so this is 4-16x larger than necessary in a temporary allocation. + // + // We should use an estimated size that *should* be enough, and then dynamically increase it + // if we hit an insufficient space error. + let bytes = + xdelta3::encode(target_bytes, source_bytes).map_err(Error::UnableToComputeDiff)?; Ok(Self { bytes }) } @@ -334,8 +340,31 @@ impl BytesDiff { } pub fn apply_xdelta(&self, source: &[u8], target: &mut Vec) -> Result<(), Error> { - *target = xdelta3::decode(&self.bytes, source).ok_or(Error::UnableToApplyDiff)?; - Ok(()) + // TODO(hdiff): Dynamic buffer allocation. This is a stopgap until we implement a schema + // change to store the output buffer size inside the `BytesDiff`. + let mut output_length = ((source.len() + self.bytes.len()) * 3) / 2; + let mut num_resizes = 0; + loop { + match xdelta3::decode_with_output_len(&self.bytes, source, output_length as u32) { + Ok(result_buffer) => { + *target = result_buffer; + + metrics::observe( + &metrics::BEACON_HDIFF_BUFFER_APPLY_RESIZES, + num_resizes as f64, + ); + return Ok(()); + } + Err(xdelta3::Error::InsufficientOutputLength) => { + // Double the output buffer length and try again. + output_length *= 2; + num_resizes += 1; + } + Err(err) => { + return Err(Error::UnableToApplyDiff(err)); + } + } + } } /// Byte size of this instance diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index e4a857b799..6a30d8a428 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -22,7 +22,6 @@ use lru::LruCache; use parking_lot::{Mutex, RwLock}; use safe_arith::SafeArith; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info, trace, warn, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use state_processing::{ @@ -37,6 +36,7 @@ use std::num::NonZeroUsize; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, error, info, trace, warn}; use types::data_column_sidecar::{ColumnIndex, DataColumnSidecar, DataColumnSidecarList}; use types::*; use zstd::{Decoder, Encoder}; @@ -73,7 +73,7 @@ pub struct HotColdDB, Cold: ItemStore> { /// Cache of beacon states. /// /// LOCK ORDERING: this lock must always be locked *after* the `split` if both are required. - state_cache: Mutex>, + pub state_cache: Mutex>, /// Cache of historic states and hierarchical diff buffers. /// /// This cache is never pruned. It is only populated in response to historical queries from the @@ -81,8 +81,6 @@ pub struct HotColdDB, Cold: ItemStore> { historic_state_cache: Mutex>, /// Chain spec. pub(crate) spec: Arc, - /// Logger. - pub log: Logger, /// Mere vessel for E. _phantom: PhantomData, } @@ -203,7 +201,6 @@ impl HotColdDB, MemoryStore> { pub fn open_ephemeral( config: StoreConfig, spec: Arc, - log: Logger, ) -> Result, MemoryStore>, Error> { config.verify::()?; @@ -218,7 +215,10 @@ impl HotColdDB, MemoryStore> { blobs_db: MemoryStore::open(), hot_db: MemoryStore::open(), block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), - state_cache: Mutex::new(StateCache::new(config.state_cache_size)), + state_cache: Mutex::new(StateCache::new( + config.state_cache_size, + config.state_cache_headroom, + )), historic_state_cache: Mutex::new(HistoricStateCache::new( config.hdiff_buffer_cache_size, config.historic_state_cache_size, @@ -226,7 +226,6 @@ impl HotColdDB, MemoryStore> { config, hierarchy, spec, - log, _phantom: PhantomData, }; @@ -246,7 +245,6 @@ impl HotColdDB, BeaconNodeBackend> { migrate_schema: impl FnOnce(Arc, SchemaVersion, SchemaVersion) -> Result<(), Error>, config: StoreConfig, spec: Arc, - log: Logger, ) -> Result, Error> { config.verify::()?; @@ -264,7 +262,10 @@ impl HotColdDB, BeaconNodeBackend> { cold_db: BeaconNodeBackend::open(&config, cold_path)?, hot_db, block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), - state_cache: Mutex::new(StateCache::new(config.state_cache_size)), + state_cache: Mutex::new(StateCache::new( + config.state_cache_size, + config.state_cache_headroom, + )), historic_state_cache: Mutex::new(HistoricStateCache::new( config.hdiff_buffer_cache_size, config.historic_state_cache_size, @@ -272,10 +273,8 @@ impl HotColdDB, BeaconNodeBackend> { config, hierarchy, spec, - log, _phantom: PhantomData, }; - // Load the config from disk but don't error on a failed read because the config itself may // need migrating. let _ = db.load_config(); @@ -287,10 +286,9 @@ impl HotColdDB, BeaconNodeBackend> { *db.split.write() = split; info!( - db.log, - "Hot-Cold DB initialized"; - "split_slot" => split.slot, - "split_state" => ?split.state_root + %split.slot, + split_state = ?split.state_root, + "Hot-Cold DB initialized" ); } @@ -352,11 +350,10 @@ impl HotColdDB, BeaconNodeBackend> { )?; info!( - db.log, - "Blob DB initialized"; - "path" => ?blobs_db_path, - "oldest_blob_slot" => ?new_blob_info.oldest_blob_slot, - "oldest_data_column_slot" => ?new_data_column_info.oldest_data_column_slot, + path = ?blobs_db_path, + oldest_blob_slot = ?new_blob_info.oldest_blob_slot, + oldest_data_column_slot = ?new_data_column_info.oldest_data_column_slot, + "Blob DB initialized" ); // Ensure that the schema version of the on-disk database matches the software. @@ -364,10 +361,9 @@ impl HotColdDB, BeaconNodeBackend> { let db = Arc::new(db); if let Some(schema_version) = db.load_schema_version()? { debug!( - db.log, - "Attempting schema migration"; - "from_version" => schema_version.as_u64(), - "to_version" => CURRENT_SCHEMA_VERSION.as_u64(), + from_version = schema_version.as_u64(), + to_version = CURRENT_SCHEMA_VERSION.as_u64(), + "Attempting schema migration" ); migrate_schema(db.clone(), schema_version, CURRENT_SCHEMA_VERSION)?; } else { @@ -385,10 +381,9 @@ impl HotColdDB, BeaconNodeBackend> { if let Ok(hierarchy_config) = disk_config.hierarchy_config() { if &db.config.hierarchy_config != hierarchy_config { info!( - db.log, - "Updating historic state config"; - "previous_config" => %hierarchy_config, - "new_config" => %db.config.hierarchy_config, + previous_config = %hierarchy_config, + new_config = %db.config.hierarchy_config, + "Updating historic state config" ); } } @@ -400,9 +395,9 @@ impl HotColdDB, BeaconNodeBackend> { // If configured, run a foreground compaction pass. if db.config.compact_on_init { - info!(db.log, "Running foreground compaction"); + info!("Running foreground compaction"); db.compact()?; - info!(db.log, "Foreground compaction complete"); + info!("Foreground compaction complete"); } Ok(db) @@ -903,7 +898,7 @@ impl, Cold: ItemStore> HotColdDB state_root: &Hash256, summary: HotStateSummary, ) -> Result<(), Error> { - self.hot_db.put(state_root, &summary).map_err(Into::into) + self.hot_db.put(state_root, &summary) } /// Store a state in the store. @@ -945,6 +940,7 @@ impl, Cold: ItemStore> HotColdDB &self, state_root: &Hash256, slot: Option, + update_cache: bool, ) -> Result>, Error> { metrics::inc_counter(&metrics::BEACON_STATE_GET_COUNT); @@ -956,10 +952,10 @@ impl, Cold: ItemStore> HotColdDB // chain. This way we avoid returning a state that doesn't match `state_root`. self.load_cold_state(state_root) } else { - self.get_hot_state(state_root) + self.get_hot_state(state_root, update_cache) } } else { - match self.get_hot_state(state_root)? { + match self.get_hot_state(state_root, update_cache)? { Some(state) => Ok(Some(state)), None => self.load_cold_state(state_root), } @@ -991,12 +987,7 @@ impl, Cold: ItemStore> HotColdDB let split = self.split.read_recursive(); if state_root != split.state_root { - warn!( - self.log, - "State cache missed"; - "state_root" => ?state_root, - "block_root" => ?block_root, - ); + warn!(?state_root, ?block_root, "State cache missed"); } // Sanity check max-slot against the split slot. @@ -1014,22 +1005,27 @@ impl, Cold: ItemStore> HotColdDB } else { state_root }; + // It's a bit redundant but we elect to cache the state here and down below. let mut opt_state = self - .load_hot_state(&state_root)? + .load_hot_state(&state_root, true)? .map(|(state, _block_root)| (state_root, state)); if let Some((state_root, state)) = opt_state.as_mut() { state.update_tree_hash_cache()?; state.build_all_caches(&self.spec)?; - self.state_cache - .lock() - .put_state(*state_root, block_root, state)?; - debug!( - self.log, - "Cached state"; - "state_root" => ?state_root, - "slot" => state.slot(), - ); + if let PutStateOutcome::New(deleted_states) = + self.state_cache + .lock() + .put_state(*state_root, block_root, state)? + { + debug!( + ?state_root, + state_slot = %state.slot(), + ?deleted_states, + location = "get_advanced_hot_state", + "Cached state", + ); + } } drop(split); Ok(opt_state) @@ -1126,6 +1122,8 @@ impl, Cold: ItemStore> HotColdDB /// Load an epoch boundary state by using the hot state summary look-up. /// /// Will fall back to the cold DB if a hot state summary is not found. + /// + /// NOTE: only used in tests at the moment pub fn load_epoch_boundary_state( &self, state_root: &Hash256, @@ -1136,9 +1134,11 @@ impl, Cold: ItemStore> HotColdDB }) = self.load_hot_state_summary(state_root)? { // NOTE: minor inefficiency here because we load an unnecessary hot state summary - let (state, _) = self.load_hot_state(&epoch_boundary_state_root)?.ok_or( - HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root), - )?; + let (state, _) = self + .load_hot_state(&epoch_boundary_state_root, true)? + .ok_or(HotColdDBError::MissingEpochBoundaryState( + epoch_boundary_state_root, + ))?; Ok(Some(state)) } else { // Try the cold DB @@ -1248,7 +1248,7 @@ impl, Cold: ItemStore> HotColdDB state_root.as_slice().to_vec(), )); - if slot.map_or(true, |slot| slot % E::slots_per_epoch() == 0) { + if slot.is_none_or(|slot| slot % E::slots_per_epoch() == 0) { key_value_batch.push(KeyValueStoreOp::DeleteKey( DBColumn::BeaconState, state_root.as_slice().to_vec(), @@ -1308,9 +1308,9 @@ impl, Cold: ItemStore> HotColdDB Ok(BlobSidecarListFromRoot::NoBlobs | BlobSidecarListFromRoot::NoRoot) => {} Err(e) => { error!( - self.log, "Error getting blobs"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error getting blobs" ); } } @@ -1333,9 +1333,9 @@ impl, Cold: ItemStore> HotColdDB } Err(e) => { error!( - self.log, "Error getting data columns"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error getting data columns" ); } } @@ -1363,10 +1363,9 @@ impl, Cold: ItemStore> HotColdDB // Rollback on failure if let Err(e) = tx_res { error!( - self.log, - "Database write failed"; - "error" => ?e, - "action" => "reverting blob DB changes" + error = ?e, + action = "reverting blob DB changes", + "Database write failed" ); let mut blob_cache_ops = blob_cache_ops; for op in blob_cache_ops.iter_mut() { @@ -1463,33 +1462,40 @@ impl, Cold: ItemStore> HotColdDB state: &BeaconState, ops: &mut Vec, ) -> Result<(), Error> { - // Put the state in the cache. - let block_root = state.get_latest_block_root(*state_root); - // Avoid storing states in the database if they already exist in the state cache. // The exception to this is the finalized state, which must exist in the cache before it // is stored on disk. - if let PutStateOutcome::Duplicate = - self.state_cache - .lock() - .put_state(*state_root, block_root, state)? - { - debug!( - self.log, - "Skipping storage of cached state"; - "slot" => state.slot(), - "state_root" => ?state_root - ); - return Ok(()); + match self.state_cache.lock().put_state( + *state_root, + state.get_latest_block_root(*state_root), + state, + )? { + PutStateOutcome::New(deleted_states) => { + debug!( + ?state_root, + state_slot = %state.slot(), + ?deleted_states, + location = "store_hot_state", + "Cached state", + ); + } + PutStateOutcome::Duplicate => { + debug!( + ?state_root, + state_slot = %state.slot(), + "State already exists in state cache", + ); + return Ok(()); + } + PutStateOutcome::Finalized => {} // Continue to store. } // On the epoch boundary, store the full state. if state.slot() % E::slots_per_epoch() == 0 { trace!( - self.log, - "Storing full state on epoch boundary"; - "slot" => state.slot().as_u64(), - "state_root" => format!("{:?}", state_root) + slot = %state.slot().as_u64(), + ?state_root, + "Storing full state on epoch boundary" ); store_full_state(state_root, state, ops)?; } @@ -1505,34 +1511,47 @@ impl, Cold: ItemStore> HotColdDB } /// Get a post-finalization state from the database or store. - pub fn get_hot_state(&self, state_root: &Hash256) -> Result>, Error> { + pub fn get_hot_state( + &self, + state_root: &Hash256, + update_cache: bool, + ) -> Result>, Error> { if let Some(state) = self.state_cache.lock().get_by_state_root(*state_root) { return Ok(Some(state)); } if *state_root != self.get_split_info().state_root { // Do not warn on start up when loading the split state. - warn!( - self.log, - "State cache missed"; - "state_root" => ?state_root, - ); + warn!(?state_root, "State cache missed"); } - let state_from_disk = self.load_hot_state(state_root)?; + let state_from_disk = self.load_hot_state(state_root, update_cache)?; if let Some((mut state, block_root)) = state_from_disk { state.update_tree_hash_cache()?; state.build_all_caches(&self.spec)?; - self.state_cache - .lock() - .put_state(*state_root, block_root, &state)?; - debug!( - self.log, - "Cached state"; - "state_root" => ?state_root, - "slot" => state.slot(), - ); + if update_cache { + if let PutStateOutcome::New(deleted_states) = + self.state_cache + .lock() + .put_state(*state_root, block_root, &state)? + { + debug!( + ?state_root, + state_slot = %state.slot(), + ?deleted_states, + location = "get_hot_state", + "Cached state", + ); + } + } else { + debug!( + ?state_root, + state_slot = %state.slot(), + "Did not cache state", + ); + } + Ok(Some(state)) } else { Ok(None) @@ -1548,6 +1567,7 @@ impl, Cold: ItemStore> HotColdDB pub fn load_hot_state( &self, state_root: &Hash256, + update_cache: bool, ) -> Result, Hash256)>, Error> { metrics::inc_counter(&metrics::BEACON_STATE_HOT_GET_COUNT); @@ -1579,26 +1599,28 @@ impl, Cold: ItemStore> HotColdDB let mut state = if slot % E::slots_per_epoch() == 0 { boundary_state } else { - // Cache ALL intermediate states that are reached during block replay. We may want - // to restrict this in future to only cache epoch boundary states. At worst we will - // cache up to 32 states for each state loaded, which should not flush out the cache - // entirely. + // If replaying blocks, and `update_cache` is true, also cache the epoch boundary + // state that this state is based on. It may be useful as the basis of more states + // in the same epoch. let state_cache_hook = |state_root, state: &mut BeaconState| { + if !update_cache || state.slot() % E::slots_per_epoch() != 0 { + return Ok(()); + } // Ensure all caches are built before attempting to cache. state.update_tree_hash_cache()?; state.build_all_caches(&self.spec)?; let latest_block_root = state.get_latest_block_root(state_root); - if let PutStateOutcome::New = + if let PutStateOutcome::New(_) = self.state_cache .lock() .put_state(state_root, latest_block_root, state)? { debug!( - self.log, - "Cached ancestor state"; - "state_root" => ?state_root, - "slot" => slot, + ?state_root, + state_slot = %state.slot(), + descendant_slot = %slot, + "Cached ancestor state", ); } Ok(()) @@ -1650,35 +1672,31 @@ impl, Cold: ItemStore> HotColdDB match self.hierarchy.storage_strategy(slot)? { StorageStrategy::ReplayFrom(from) => { debug!( - self.log, - "Storing cold state"; - "strategy" => "replay", - "from_slot" => from, - "slot" => state.slot(), + strategy = "replay", + from_slot = %from, + %slot, + "Storing cold state", ); // Already have persisted the state summary, don't persist anything else } StorageStrategy::Snapshot => { debug!( - self.log, - "Storing cold state"; - "strategy" => "snapshot", - "slot" => state.slot(), + strategy = "snapshot", + %slot, + "Storing cold state" ); self.store_cold_state_as_snapshot(state, ops)?; } StorageStrategy::DiffFrom(from) => { debug!( - self.log, - "Storing cold state"; - "strategy" => "diff", - "from_slot" => from, - "slot" => state.slot(), + strategy = "diff", + from_slot = %from, + %slot, + "Storing cold state" ); self.store_cold_state_as_diff(state, from, ops)?; } } - Ok(()) } @@ -1837,10 +1855,9 @@ impl, Cold: ItemStore> HotColdDB metrics::start_timer(&metrics::STORE_BEACON_COLD_BUILD_BEACON_CACHES_TIME); base_state.build_all_caches(&self.spec)?; debug!( - self.log, - "Built caches for historic state"; - "target_slot" => slot, - "build_time_ms" => metrics::stop_timer_with_duration(cache_timer).as_millis() + target_slot = %slot, + build_time_ms = metrics::stop_timer_with_duration(cache_timer).as_millis(), + "Built caches for historic state" ); self.historic_state_cache .lock() @@ -1862,10 +1879,9 @@ impl, Cold: ItemStore> HotColdDB })?; let state = self.replay_blocks(base_state, blocks, slot, Some(state_root_iter), None)?; debug!( - self.log, - "Replayed blocks for historic state"; - "target_slot" => slot, - "replay_time_ms" => metrics::stop_timer_with_duration(replay_timer).as_millis() + target_slot = %slot, + replay_time_ms = metrics::stop_timer_with_duration(replay_timer).as_millis(), + "Replayed blocks for historic state" ); self.historic_state_cache @@ -1893,9 +1909,8 @@ impl, Cold: ItemStore> HotColdDB fn load_hdiff_buffer_for_slot(&self, slot: Slot) -> Result<(Slot, HDiffBuffer), Error> { if let Some(buffer) = self.historic_state_cache.lock().get_hdiff_buffer(slot) { debug!( - self.log, - "Hit hdiff buffer cache"; - "slot" => slot + %slot, + "Hit hdiff buffer cache" ); metrics::inc_counter(&metrics::STORE_BEACON_HDIFF_BUFFER_CACHE_HIT); return Ok((slot, buffer)); @@ -1919,10 +1934,9 @@ impl, Cold: ItemStore> HotColdDB let load_time_ms = t.elapsed().as_millis(); debug!( - self.log, - "Cached state and hdiff buffer"; - "load_time_ms" => load_time_ms, - "slot" => slot + load_time_ms, + %slot, + "Cached state and hdiff buffer" ); Ok((slot, buffer)) @@ -1945,10 +1959,9 @@ impl, Cold: ItemStore> HotColdDB let load_time_ms = t.elapsed().as_millis(); debug!( - self.log, - "Cached hdiff buffer"; - "load_time_ms" => load_time_ms, - "slot" => slot + load_time_ms, + %slot, + "Cached hdiff buffer" ); Ok((slot, buffer)) @@ -2052,9 +2065,8 @@ impl, Cold: ItemStore> HotColdDB .map(|block_replayer| { if have_state_root_iterator && block_replayer.state_root_miss() { warn!( - self.log, - "State root cache miss during block replay"; - "slot" => target_slot, + slot = %target_slot, + "State root cache miss during block replay" ); } block_replayer.into_state() @@ -2180,11 +2192,6 @@ impl, Cold: ItemStore> HotColdDB &self.spec } - /// Get a reference to the `Logger` used by the database. - pub fn logger(&self) -> &Logger { - &self.log - } - /// Fetch a copy of the current split slot from memory. pub fn get_split_slot(&self) -> Slot { self.split.read_recursive().slot @@ -2579,17 +2586,9 @@ impl, Cold: ItemStore> HotColdDB columns.extend(previous_schema_columns); for column in columns { - info!( - self.log, - "Starting compaction"; - "column" => ?column - ); + info!(?column, "Starting compaction"); self.cold_db.compact_column(column)?; - info!( - self.log, - "Finishing compaction"; - "column" => ?column - ); + info!(?column, "Finishing compaction"); } Ok(()) } @@ -2668,10 +2667,15 @@ impl, Cold: ItemStore> HotColdDB return Ok(()); }; - // Load the split state so we can backtrack to find execution payloads. - let split_state = self.get_state(&split.state_root, Some(split.slot))?.ok_or( - HotColdDBError::MissingSplitState(split.state_root, split.slot), - )?; + // Load the split state so we can backtrack to find execution payloads. The split state + // should be in the state cache as the enshrined finalized state, so this should never + // cache miss. + let split_state = self + .get_state(&split.state_root, Some(split.slot), true)? + .ok_or(HotColdDBError::MissingSplitState( + split.state_root, + split.slot, + ))?; // The finalized block may or may not have its execution payload stored, depending on // whether it was at a skipped slot. However for a fully pruned database its parent @@ -2690,16 +2694,15 @@ impl, Cold: ItemStore> HotColdDB })??; if already_pruned && !force { - info!(self.log, "Execution payloads are pruned"); + info!("Execution payloads are pruned"); return Ok(()); } // Iterate block roots backwards to the Bellatrix fork or the anchor slot, whichever comes // first. warn!( - self.log, - "Pruning finalized payloads"; - "info" => "you may notice degraded I/O performance while this runs" + info = "you may notice degraded I/O performance while this runs", + "Pruning finalized payloads" ); let anchor_info = self.get_anchor_info(); @@ -2713,58 +2716,41 @@ impl, Cold: ItemStore> HotColdDB Ok(tuple) => tuple, Err(e) => { warn!( - self.log, - "Stopping payload pruning early"; - "error" => ?e, + error = ?e, + "Stopping payload pruning early" ); break; } }; if slot < bellatrix_fork_slot { - info!( - self.log, - "Payload pruning reached Bellatrix boundary"; - ); + info!("Payload pruning reached Bellatrix boundary"); break; } if Some(block_root) != last_pruned_block_root && self.execution_payload_exists(&block_root)? { - debug!( - self.log, - "Pruning execution payload"; - "slot" => slot, - "block_root" => ?block_root, - ); + debug!(%slot, ?block_root, "Pruning execution payload"); last_pruned_block_root = Some(block_root); ops.push(StoreOp::DeleteExecutionPayload(block_root)); } if slot <= anchor_info.oldest_block_slot { - info!( - self.log, - "Payload pruning reached anchor oldest block slot"; - "slot" => slot - ); + info!(%slot, "Payload pruning reached anchor oldest block slot"); break; } } let payloads_pruned = ops.len(); self.do_atomically_with_block_and_blobs_cache(ops)?; - info!( - self.log, - "Execution payload pruning complete"; - "payloads_pruned" => payloads_pruned, - ); + info!(%payloads_pruned, "Execution payload pruning complete"); Ok(()) } /// Try to prune blobs, approximating the current epoch from the split slot. pub fn try_prune_most_blobs(&self, force: bool) -> Result<(), Error> { let Some(deneb_fork_epoch) = self.spec.deneb_fork_epoch else { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Ok(()); }; // The current epoch is >= split_epoch + 2. It could be greater if the database is @@ -2795,7 +2781,7 @@ impl, Cold: ItemStore> HotColdDB data_availability_boundary: Epoch, ) -> Result<(), Error> { if self.spec.deneb_fork_epoch.is_none() { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Ok(()); } @@ -2804,17 +2790,13 @@ impl, Cold: ItemStore> HotColdDB let epochs_per_blob_prune = self.get_config().epochs_per_blob_prune; if !force && !pruning_enabled { - debug!( - self.log, - "Blob pruning is disabled"; - "prune_blobs" => pruning_enabled - ); + debug!(prune_blobs = pruning_enabled, "Blob pruning is disabled"); return Ok(()); } let blob_info = self.get_blob_info(); let Some(oldest_blob_slot) = blob_info.oldest_blob_slot else { - error!(self.log, "Slot of oldest blob is not known"); + error!("Slot of oldest blob is not known"); return Err(HotColdDBError::BlobPruneLogicError.into()); }; @@ -2837,13 +2819,12 @@ impl, Cold: ItemStore> HotColdDB if !force && !should_prune || !can_prune { debug!( - self.log, - "Blobs are pruned"; - "oldest_blob_slot" => oldest_blob_slot, - "data_availability_boundary" => data_availability_boundary, - "split_slot" => split.slot, - "end_epoch" => end_epoch, - "start_epoch" => start_epoch, + %oldest_blob_slot, + %data_availability_boundary, + %split.slot, + %end_epoch, + %start_epoch, + "Blobs are pruned" ); return Ok(()); } @@ -2852,21 +2833,19 @@ impl, Cold: ItemStore> HotColdDB let anchor = self.get_anchor_info(); if oldest_blob_slot < anchor.oldest_block_slot { error!( - self.log, - "Oldest blob is older than oldest block"; - "oldest_blob_slot" => oldest_blob_slot, - "oldest_block_slot" => anchor.oldest_block_slot + %oldest_blob_slot, + oldest_block_slot = %anchor.oldest_block_slot, + "Oldest blob is older than oldest block" ); return Err(HotColdDBError::BlobPruneLogicError.into()); } // Iterate block roots forwards from the oldest blob slot. debug!( - self.log, - "Pruning blobs"; - "start_epoch" => start_epoch, - "end_epoch" => end_epoch, - "data_availability_boundary" => data_availability_boundary, + %start_epoch, + %end_epoch, + %data_availability_boundary, + "Pruning blobs" ); // We collect block roots of deleted blobs in memory. Even for 10y of blob history this @@ -2922,10 +2901,7 @@ impl, Cold: ItemStore> HotColdDB let op = self.compare_and_set_blob_info(blob_info, new_blob_info)?; self.do_atomically_with_block_and_blobs_cache(vec![StoreOp::KeyValueOp(op)])?; - debug!( - self.log, - "Blob pruning complete"; - ); + debug!("Blob pruning complete"); Ok(()) } @@ -2995,18 +2971,13 @@ impl, Cold: ItemStore> HotColdDB // If we just deleted the genesis state, re-store it using the current* schema. if self.get_split_slot() > 0 { info!( - self.log, - "Re-storing genesis state"; - "state_root" => ?genesis_state_root, + state_root = ?genesis_state_root, + "Re-storing genesis state" ); self.store_cold_state(&genesis_state_root, genesis_state, &mut cold_ops)?; } - info!( - self.log, - "Deleting historic states"; - "delete_ops" => delete_ops, - ); + info!(delete_ops, "Deleting historic states"); self.cold_db.do_atomically(cold_ops)?; // In order to reclaim space, we need to compact the freezer DB as well. @@ -3022,9 +2993,8 @@ impl, Cold: ItemStore> HotColdDB pub fn prune_old_hot_states(&self) -> Result<(), Error> { let split = self.get_split_info(); debug!( - self.log, - "Database state pruning started"; - "split_slot" => split.slot, + %split.slot, + "Database state pruning started" ); let mut state_delete_batch = vec![]; for res in self @@ -3046,11 +3016,10 @@ impl, Cold: ItemStore> HotColdDB "non-canonical" }; debug!( - self.log, - "Deleting state"; - "state_root" => ?state_root, - "slot" => summary.slot, - "reason" => reason, + ?state_root, + slot = %summary.slot, + %reason, + "Deleting state" ); state_delete_batch.push(StoreOp::DeleteState(state_root, Some(summary.slot))); } @@ -3058,11 +3027,7 @@ impl, Cold: ItemStore> HotColdDB } let num_deleted_states = state_delete_batch.len(); self.do_atomically_with_block_and_blobs_cache(state_delete_batch)?; - debug!( - self.log, - "Database state pruning complete"; - "num_deleted_states" => num_deleted_states, - ); + debug!(%num_deleted_states, "Database state pruning complete"); Ok(()) } } @@ -3075,9 +3040,8 @@ pub fn migrate_database, Cold: ItemStore>( finalized_state: &BeaconState, ) -> Result<(), Error> { debug!( - store.log, - "Freezer migration started"; - "slot" => finalized_state.slot() + slot = %finalized_state.slot(), + "Freezer migration started" ); // 0. Check that the migration is sensible. @@ -3153,7 +3117,7 @@ pub fn migrate_database, Cold: ItemStore>( // stored (see `STATE_UPPER_LIMIT_NO_RETAIN`). Make an exception for the genesis state // which always needs to be copied from the hot DB to the freezer and should not be deleted. if slot != 0 && slot < anchor_info.state_upper_limit { - debug!(store.log, "Pruning finalized state"; "slot" => slot); + debug!(%slot, "Pruning finalized state"); continue; } @@ -3169,8 +3133,10 @@ pub fn migrate_database, Cold: ItemStore>( // Store slot -> state_root and state_root -> slot mappings. store.store_cold_state_summary(&state_root, slot, &mut cold_db_ops)?; } else { + // This is some state that we want to migrate to the freezer db. + // There is no reason to cache this state. let state: BeaconState = store - .get_hot_state(&state_root)? + .get_hot_state(&state_root, false)? .ok_or(HotColdDBError::MissingStateToFreeze(state_root))?; store.store_cold_state(&state_root, &state, &mut cold_db_ops)?; @@ -3213,10 +3179,9 @@ pub fn migrate_database, Cold: ItemStore>( // place in code. if latest_split_slot != current_split_slot { error!( - store.log, - "Race condition detected: Split point changed while moving states to the freezer"; - "previous split slot" => current_split_slot, - "current split slot" => latest_split_slot, + previous_split_slot = %current_split_slot, + current_split_slot = %latest_split_slot, + "Race condition detected: Split point changed while moving states to the freezer" ); // Assume the freezing procedure will be retried in case this happens. @@ -3252,9 +3217,8 @@ pub fn migrate_database, Cold: ItemStore>( )?; debug!( - store.log, - "Freezer migration complete"; - "slot" => finalized_state.slot() + slot = %finalized_state.slot(), + "Freezer migration complete" ); Ok(()) diff --git a/beacon_node/store/src/iter.rs b/beacon_node/store/src/iter.rs index 97a88c01c8..8419dde4a2 100644 --- a/beacon_node/store/src/iter.rs +++ b/beacon_node/store/src/iter.rs @@ -27,8 +27,10 @@ impl<'a, E: EthSpec, Hot: ItemStore, Cold: ItemStore> &self, store: &'a HotColdDB, ) -> Option> { + // Ancestor roots and their states are probably in the cold db + // but we set `update_cache` to false just in case let state = store - .get_state(&self.message().state_root(), Some(self.slot())) + .get_state(&self.message().state_root(), Some(self.slot()), false) .ok()??; Some(BlockRootsIterator::owned(store, state)) @@ -189,8 +191,10 @@ impl<'a, E: EthSpec, Hot: ItemStore, Cold: ItemStore> RootsIterator<'a, E, let block = store .get_blinded_block(&block_hash)? .ok_or_else(|| BeaconStateError::MissingBeaconBlock(block_hash.into()))?; + // We are querying some block from the database. It's not clear if the block's state is useful, + // we elect not to cache it. let state = store - .get_state(&block.state_root(), Some(block.slot()))? + .get_state(&block.state_root(), Some(block.slot()), false)? .ok_or_else(|| BeaconStateError::MissingBeaconState(block.state_root().into()))?; Ok(Self::owned(store, state)) } @@ -362,8 +366,9 @@ fn next_historical_root_backtrack_state, Cold: Ite if new_state_slot >= historic_state_upper_limit { let new_state_root = current_state.get_state_root(new_state_slot)?; + // We are backtracking through historical states, we don't want to cache these. Ok(store - .get_state(new_state_root, Some(new_state_slot))? + .get_state(new_state_root, Some(new_state_slot), false)? .ok_or_else(|| BeaconStateError::MissingBeaconState((*new_state_root).into()))?) } else { Err(Error::HistoryUnavailable) @@ -382,7 +387,6 @@ mod test { use crate::StoreConfig as Config; use beacon_chain::test_utils::BeaconChainHarness; use beacon_chain::types::{ChainSpec, MainnetEthSpec}; - use sloggers::{null::NullLoggerBuilder, Build}; use std::sync::Arc; use types::FixedBytesExtended; @@ -398,10 +402,8 @@ mod test { #[test] fn block_root_iter() { - let log = NullLoggerBuilder.build().unwrap(); let store = - HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal()), log) - .unwrap(); + HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal())).unwrap(); let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); let mut state_a: BeaconState = get_state(); @@ -447,10 +449,8 @@ mod test { #[test] fn state_root_iter() { - let log = NullLoggerBuilder.build().unwrap(); let store = - HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal()), log) - .unwrap(); + HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal())).unwrap(); let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); let mut state_a: BeaconState = get_state(); diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 0cfc42ab15..2b5be03489 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -195,7 +195,6 @@ pub trait ItemStore: KeyValueStore + Sync + Send + Sized + 'stati let key = key.as_slice(); self.put_bytes(column, key, &item.as_store_bytes()) - .map_err(Into::into) } fn put_sync(&self, key: &Hash256, item: &I) -> Result<(), Error> { @@ -203,7 +202,6 @@ pub trait ItemStore: KeyValueStore + Sync + Send + Sized + 'stati let key = key.as_slice(); self.put_bytes_sync(column, key, &item.as_store_bytes()) - .map_err(Into::into) } /// Retrieve an item from `Self`. diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs index 6f9f667917..5da73c3cad 100644 --- a/beacon_node/store/src/metrics.rs +++ b/beacon_node/store/src/metrics.rs @@ -202,6 +202,13 @@ pub static BEACON_HDIFF_BUFFER_CLONE_TIMES: LazyLock> = LazyLo "Time required to clone hierarchical diff buffer bytes", ) }); +pub static BEACON_HDIFF_BUFFER_APPLY_RESIZES: LazyLock> = LazyLock::new(|| { + try_create_histogram_with_buckets( + "store_hdiff_buffer_apply_resizes", + "Number of times during diff application that the output buffer had to be resized before decoding succeeded", + Ok(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) + ) +}); /* * Beacon Block */ diff --git a/beacon_node/store/src/reconstruct.rs b/beacon_node/store/src/reconstruct.rs index 2a3b208aae..30df552b7b 100644 --- a/beacon_node/store/src/reconstruct.rs +++ b/beacon_node/store/src/reconstruct.rs @@ -4,12 +4,12 @@ use crate::metadata::ANCHOR_FOR_ARCHIVE_NODE; use crate::metrics; use crate::{Error, ItemStore}; use itertools::{process_results, Itertools}; -use slog::{debug, info}; use state_processing::{ per_block_processing, per_slot_processing, BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, }; use std::sync::Arc; +use tracing::{debug, info}; use types::EthSpec; impl HotColdDB @@ -37,9 +37,8 @@ where } debug!( - self.log, - "Starting state reconstruction batch"; - "start_slot" => anchor.state_lower_limit, + start_slot = %anchor.state_lower_limit, + "Starting state reconstruction batch" ); let _t = metrics::start_timer(&metrics::STORE_BEACON_RECONSTRUCTION_TIME); @@ -124,10 +123,9 @@ where || reconstruction_complete { info!( - self.log, - "State reconstruction in progress"; - "slot" => slot, - "remaining" => upper_limit_slot - 1 - slot + %slot, + remaining = %(upper_limit_slot - 1 - slot), + "State reconstruction in progress" ); self.cold_db.do_atomically(std::mem::take(&mut io_batch))?; @@ -164,10 +162,9 @@ where // batch when there is idle capacity. if batch_complete { debug!( - self.log, - "Finished state reconstruction batch"; - "start_slot" => lower_limit_slot, - "end_slot" => slot, + start_slot = %lower_limit_slot, + end_slot = %slot, + "Finished state reconstruction batch" ); return Ok(()); } diff --git a/beacon_node/store/src/state_cache.rs b/beacon_node/store/src/state_cache.rs index 96e4de4639..281ecab152 100644 --- a/beacon_node/store/src/state_cache.rs +++ b/beacon_node/store/src/state_cache.rs @@ -33,26 +33,33 @@ pub struct SlotMap { #[derive(Debug)] pub struct StateCache { finalized_state: Option>, - states: LruCache>, + // Stores the tuple (state_root, state) as LruCache only returns the value on put and we need + // the state_root + states: LruCache)>, block_map: BlockMap, max_epoch: Epoch, + head_block_root: Hash256, + headroom: NonZeroUsize, } #[derive(Debug)] pub enum PutStateOutcome { Finalized, Duplicate, - New, + /// Includes deleted states as a result of this insertion + New(Vec), } #[allow(clippy::len_without_is_empty)] impl StateCache { - pub fn new(capacity: NonZeroUsize) -> Self { + pub fn new(capacity: NonZeroUsize, headroom: NonZeroUsize) -> Self { StateCache { finalized_state: None, states: LruCache::new(capacity), block_map: BlockMap::default(), max_epoch: Epoch::new(0), + head_block_root: Hash256::ZERO, + headroom, } } @@ -98,6 +105,13 @@ impl StateCache { Ok(()) } + /// Update the state cache's view of the enshrined head block. + /// + /// We never prune the unadvanced state for the head block. + pub fn update_head_block_root(&mut self, head_block_root: Hash256) { + self.head_block_root = head_block_root; + } + /// Rebase the given state on the finalized state in order to reduce its memory consumption. /// /// This function should only be called on states that are likely not to already share tree @@ -147,18 +161,26 @@ impl StateCache { self.max_epoch = std::cmp::max(state.current_epoch(), self.max_epoch); // If the cache is full, use the custom cull routine to make room. - if let Some(over_capacity) = self.len().checked_sub(self.capacity()) { - self.cull(over_capacity + 1); - } + let mut deleted_states = + if let Some(over_capacity) = self.len().checked_sub(self.capacity()) { + // The `over_capacity` should always be 0, but we add it here just in case. + self.cull(over_capacity + self.headroom.get()) + } else { + vec![] + }; // Insert the full state into the cache. - self.states.put(state_root, state.clone()); + if let Some((deleted_state_root, _)) = + self.states.put(state_root, (state_root, state.clone())) + { + deleted_states.push(deleted_state_root); + } // Record the connection from block root and slot to this state. let slot = state.slot(); self.block_map.insert(block_root, slot, state_root); - Ok(PutStateOutcome::New) + Ok(PutStateOutcome::New(deleted_states)) } pub fn get_by_state_root(&mut self, state_root: Hash256) -> Option> { @@ -167,7 +189,7 @@ impl StateCache { return Some(finalized_state.state.clone()); } } - self.states.get(&state_root).cloned() + self.states.get(&state_root).map(|(_, state)| state.clone()) } pub fn get_by_block_root( @@ -211,7 +233,7 @@ impl StateCache { /// - Mid-epoch unadvanced states. /// - Epoch-boundary states that are too old to be finalized. /// - Epoch-boundary states that could be finalized. - pub fn cull(&mut self, count: usize) { + pub fn cull(&mut self, count: usize) -> Vec { let cull_exempt = std::cmp::max( 1, self.len() * CULL_EXEMPT_NUMERATOR / CULL_EXEMPT_DENOMINATOR, @@ -222,7 +244,8 @@ impl StateCache { let mut mid_epoch_state_roots = vec![]; let mut old_boundary_state_roots = vec![]; let mut good_boundary_state_roots = vec![]; - for (&state_root, state) in self.states.iter().skip(cull_exempt) { + + for (&state_root, (_, state)) in self.states.iter().skip(cull_exempt) { let is_advanced = state.slot() > state.latest_block_header().slot; let is_boundary = state.slot() % E::slots_per_epoch() == 0; let could_finalize = @@ -236,7 +259,8 @@ impl StateCache { } } else if is_advanced { advanced_state_roots.push(state_root); - } else { + } else if state.get_latest_block_root(state_root) != self.head_block_root { + // Never prune the head state mid_epoch_state_roots.push(state_root); } @@ -248,15 +272,19 @@ impl StateCache { // Stage 2: delete. // This could probably be more efficient in how it interacts with the block map. - for state_root in advanced_state_roots - .iter() - .chain(mid_epoch_state_roots.iter()) - .chain(old_boundary_state_roots.iter()) - .chain(good_boundary_state_roots.iter()) + let state_roots_to_delete = advanced_state_roots + .into_iter() + .chain(old_boundary_state_roots) + .chain(mid_epoch_state_roots) + .chain(good_boundary_state_roots) .take(count) - { + .collect::>(); + + for state_root in &state_roots_to_delete { self.delete_state(state_root); } + + state_roots_to_delete } } diff --git a/beacon_node/tests/test.rs b/beacon_node/tests/test.rs index 0738b12ec0..0d448e6c06 100644 --- a/beacon_node/tests/test.rs +++ b/beacon_node/tests/test.rs @@ -25,8 +25,6 @@ fn build_node(env: &mut Environment) -> LocalBeaconNode { #[test] fn http_server_genesis_state() { let mut env = env_builder() - .test_logger() - .expect("should build env logger") .multi_threaded_tokio_runtime() .expect("should start tokio runtime") .build() diff --git a/beacon_node/timer/Cargo.toml b/beacon_node/timer/Cargo.toml index 546cc2ed41..53fa2c0132 100644 --- a/beacon_node/timer/Cargo.toml +++ b/beacon_node/timer/Cargo.toml @@ -6,7 +6,7 @@ edition = { workspace = true } [dependencies] beacon_chain = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } diff --git a/beacon_node/timer/src/lib.rs b/beacon_node/timer/src/lib.rs index 7c2db69604..1bd1c1e8ea 100644 --- a/beacon_node/timer/src/lib.rs +++ b/beacon_node/timer/src/lib.rs @@ -3,22 +3,21 @@ //! This service allows task execution on the beacon node for various functionality. use beacon_chain::{BeaconChain, BeaconChainTypes}; -use slog::{info, warn}; use slot_clock::SlotClock; use std::sync::Arc; use tokio::time::sleep; +use tracing::{info, warn}; /// Spawns a timer service which periodically executes tasks for the beacon chain pub fn spawn_timer( executor: task_executor::TaskExecutor, beacon_chain: Arc>, ) -> Result<(), &'static str> { - let log = executor.log().clone(); let timer_future = async move { loop { let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() else { - warn!(log, "Unable to determine duration to next slot"); + warn!("Unable to determine duration to next slot"); return; }; @@ -28,7 +27,7 @@ pub fn spawn_timer( }; executor.spawn(timer_future, "timer"); - info!(executor.log(), "Timer service started"); + info!("Timer service started"); Ok(()) } diff --git a/book/.markdownlint.yml b/book/.markdownlint.yml index 5d6bda29f1..4f7d113364 100644 --- a/book/.markdownlint.yml +++ b/book/.markdownlint.yml @@ -8,7 +8,7 @@ MD010: MD013: false # MD028: set to false to allow blank line between blockquote: https://github.com/DavidAnson/markdownlint/blob/main/doc/md028.md -# This is because the blockquotes are shown separatedly (a deisred outcome) when having a blank line in between +# This is because the blockquotes are shown separately (a desired outcome) when having a blank line in between MD028: false # MD024: set siblings_only to true so that same headings with different parent headings are allowed diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 44d7702e5f..3d09e3a6a5 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -2,58 +2,54 @@ * [Introduction](./intro.md) * [Installation](./installation.md) - * [Pre-Built Binaries](./installation-binaries.md) - * [Docker](./docker.md) - * [Build from Source](./installation-source.md) - * [Raspberry Pi 4](./pi.md) - * [Cross-Compiling](./cross-compiling.md) - * [Homebrew](./homebrew.md) - * [Update Priorities](./installation-priorities.md) + * [Pre-Built Binaries](./installation_binaries.md) + * [Docker](./installation_docker.md) + * [Build from Source](./installation_source.md) + * [Cross-Compiling](./installation_cross_compiling.md) + * [Homebrew](./installation_homebrew.md) + * [Update Priorities](./installation_priorities.md) * [Run a Node](./run_a_node.md) -* [Become a Validator](./mainnet-validator.md) -* [Validator Management](./validator-management.md) - * [The `validator-manager` Command](./validator-manager.md) - * [Creating validators](./validator-manager-create.md) - * [Moving validators](./validator-manager-move.md) - * [Managing validators](./validator-manager-api.md) - * [Slashing Protection](./slashing-protection.md) - * [Voluntary Exits](./voluntary-exit.md) - * [Partial Withdrawals](./partial-withdrawal.md) - * [Validator Monitoring](./validator-monitoring.md) - * [Doppelganger Protection](./validator-doppelganger.md) - * [Suggested Fee Recipient](./suggested-fee-recipient.md) - * [Validator Graffiti](./graffiti.md) +* [Become a Validator](./mainnet_validator.md) +* [Validator Management](./validator_management.md) + * [The `validator-manager` Command](./validator_manager.md) + * [Creating validators](./validator_manager_create.md) + * [Moving validators](./validator_manager_move.md) + * [Managing validators](./validator_manager_api.md) + * [Slashing Protection](./validator_slashing_protection.md) + * [Voluntary Exits](./validator_voluntary_exit.md) + * [Validator Sweep](./validator_sweep.md) + * [Validator Monitoring](./validator_monitoring.md) + * [Doppelganger Protection](./validator_doppelganger.md) + * [Suggested Fee Recipient](./validator_fee_recipient.md) + * [Validator Graffiti](./validator_graffiti.md) * [APIs](./api.md) - * [Beacon Node API](./api-bn.md) - * [Lighthouse API](./api-lighthouse.md) - * [Validator Inclusion APIs](./validator-inclusion.md) - * [Validator Client API](./api-vc.md) - * [Endpoints](./api-vc-endpoints.md) - * [Authorization Header](./api-vc-auth-header.md) - * [Signature Header](./api-vc-sig-header.md) - * [Prometheus Metrics](./advanced_metrics.md) -* [Lighthouse UI (Siren)](./lighthouse-ui.md) - * [Configuration](./ui-configuration.md) - * [Authentication](./ui-authentication.md) - * [Usage](./ui-usage.md) - * [FAQs](./ui-faqs.md) + * [Beacon Node API](./api_bn.md) + * [Lighthouse API](./api_lighthouse.md) + * [Validator Inclusion APIs](./api_validator_inclusion.md) + * [Validator Client API](./api_vc.md) + * [Endpoints](./api_vc_endpoints.md) + * [Authorization Header](./api_vc_auth_header.md) + * [Prometheus Metrics](./api_metrics.md) +* [Lighthouse UI (Siren)](./ui.md) + * [Configuration](./ui_configuration.md) + * [Authentication](./ui_authentication.md) + * [Usage](./ui_usage.md) + * [FAQs](./ui_faqs.md) * [Advanced Usage](./advanced.md) - * [Checkpoint Sync](./checkpoint-sync.md) - * [Custom Data Directories](./advanced-datadir.md) - * [Proposer Only Beacon Nodes](./advanced-proposer-only.md) - * [Remote Signing with Web3Signer](./validator-web3signer.md) + * [Checkpoint Sync](./advanced_checkpoint_sync.md) + * [Custom Data Directories](./advanced_datadir.md) + * [Proposer Only Beacon Nodes](./advanced_proposer_only.md) + * [Remote Signing with Web3Signer](./advanced_web3signer.md) * [Database Configuration](./advanced_database.md) - * [Database Migrations](./database-migrations.md) - * [Key Management (Deprecated)](./key-management.md) - * [Key Recovery](./key-recovery.md) + * [Database Migrations](./advanced_database_migrations.md) + * [Key Recovery](./advanced_key_recovery.md) * [Advanced Networking](./advanced_networking.md) - * [Running a Slasher](./slasher.md) - * [Redundancy](./redundancy.md) - * [Release Candidates](./advanced-release-candidates.md) - * [MEV](./builders.md) - * [Merge Migration](./merge-migration.md) - * [Late Block Re-orgs](./late-block-re-orgs.md) - * [Blobs](./advanced-blobs.md) + * [Running a Slasher](./advanced_slasher.md) + * [Redundancy](./advanced_redundancy.md) + * [Release Candidates](./advanced_release_candidates.md) + * [MEV](./advanced_builders.md) + * [Late Block Re-orgs](./advanced_re-orgs.md) + * [Blobs](./advanced_blobs.md) * [Command Line Reference (CLI)](./help_general.md) * [Beacon Node](./help_bn.md) * [Validator Client](./help_vc.md) @@ -62,7 +58,11 @@ * [Import](./help_vm_import.md) * [Move](./help_vm_move.md) * [Contributing](./contributing.md) - * [Development Environment](./setup.md) + * [Development Environment](./contributing_setup.md) * [FAQs](./faq.md) * [Protocol Developers](./developers.md) * [Security Researchers](./security.md) +* [Archived](./archived.md) + * [Merge Migration](./archived_merge_migration.md) + * [Raspberry Pi 4](./archived_pi.md) + * [Key Management](./archived_key_management.md) diff --git a/book/src/advanced.md b/book/src/advanced.md index 1a882835a4..76a7fed202 100644 --- a/book/src/advanced.md +++ b/book/src/advanced.md @@ -6,19 +6,17 @@ elsewhere? This section provides detailed information about configuring Lighthouse for specific use cases, and tips about how things work under the hood. -* [Checkpoint Sync](./checkpoint-sync.md): quickly sync the beacon chain to perform validator duties. -* [Custom Data Directories](./advanced-datadir.md): modify the data directory to your preferred location. -* [Proposer Only Beacon Nodes](./advanced-proposer-only.md): beacon node only for proposer duty for increased anonymity. -* [Remote Signing with Web3Signer](./validator-web3signer.md): don't want to store your keystore in local node? Use web3signer. +* [Checkpoint Sync](./advanced_checkpoint_sync.md): quickly sync the beacon chain to perform validator duties. +* [Custom Data Directories](./advanced_datadir.md): modify the data directory to your preferred location. +* [Proposer Only Beacon Nodes](./advanced_proposer_only.md): beacon node only for proposer duty for increased anonymity. +* [Remote Signing with Web3Signer](./advanced_web3signer.md): don't want to store your keystore in local node? Use web3signer. * [Database Configuration](./advanced_database.md): understanding space-time trade-offs in the database. -* [Database Migrations](./database-migrations.md): have a look at all previous Lighthouse database scheme versions. -* [Key Management](./key-management.md): explore how to generate wallet with Lighthouse. -* [Key Recovery](./key-recovery.md): explore how to recover wallet and validator with Lighthouse. +* [Database Migrations](./advanced_database_migrations.md): have a look at all previous Lighthouse database scheme versions. +* [Key Recovery](./advanced_key_recovery.md): explore how to recover wallet and validator with Lighthouse. * [Advanced Networking](./advanced_networking.md): open your ports to have a diverse and healthy set of peers. -* [Running a Slasher](./slasher.md): contribute to the health of the network by running a slasher. -* [Redundancy](./redundancy.md): want to have more than one beacon node as backup? This is for you. -* [Release Candidates](./advanced-release-candidates.md): latest release of Lighthouse to get feedback from users. -* [Maximal Extractable Value](./builders.md): use external builders for a potential higher rewards during block proposals -* [Merge Migration](./merge-migration.md): look at what you need to do during a significant network upgrade: The Merge -* [Late Block Re-orgs](./late-block-re-orgs.md): read information about Lighthouse late block re-orgs. -* [Blobs](./advanced-blobs.md): information about blobs in Deneb upgrade +* [Running a Slasher](./advanced_slasher.md): contribute to the health of the network by running a slasher. +* [Redundancy](./advanced_redundancy.md): want to have more than one beacon node as backup? This is for you. +* [Release Candidates](./advanced_release_candidates.md): latest release of Lighthouse to get feedback from users. +* [Maximal Extractable Value](./advanced_builders.md): use external builders for a potential higher rewards during block proposals +* [Late Block Re-orgs](./advanced_re-orgs.md): read information about Lighthouse late block re-orgs. +* [Blobs](./advanced_blobs.md): information about blobs in Deneb upgrade diff --git a/book/src/advanced-blobs.md b/book/src/advanced_blobs.md similarity index 96% rename from book/src/advanced-blobs.md rename to book/src/advanced_blobs.md index 785bd5797d..aa995b8e1d 100644 --- a/book/src/advanced-blobs.md +++ b/book/src/advanced_blobs.md @@ -38,4 +38,4 @@ In the Deneb network upgrade, one of the changes is the implementation of EIP-48 curl "http://localhost:5052/lighthouse/database/info" | jq ``` - Refer to [Lighthouse API](./api-lighthouse.md#lighthousedatabaseinfo) for an example response. + Refer to [Lighthouse API](./api_lighthouse.md#lighthousedatabaseinfo) for an example response. diff --git a/book/src/builders.md b/book/src/advanced_builders.md similarity index 95% rename from book/src/builders.md rename to book/src/advanced_builders.md index 5b8e9ddb8b..d9468898b4 100644 --- a/book/src/builders.md +++ b/book/src/advanced_builders.md @@ -83,11 +83,11 @@ is something afoot. To update gas limit per-validator you can use the [standard key manager API][gas-limit-api]. -Alternatively, you can use the [lighthouse API](api-vc-endpoints.md). See below for an example. +Alternatively, you can use the [lighthouse API](api_vc_endpoints.md). See below for an example. ### Enable/Disable builder proposals via HTTP -Use the [lighthouse API](api-vc-endpoints.md) to enable/disable use of the builder API on a per-validator basis. +Use the [lighthouse API](api_vc_endpoints.md) to enable/disable use of the builder API on a per-validator basis. You can also update the configured gas limit with these requests. #### `PATCH /lighthouse/validators/:voting_pubkey` @@ -98,7 +98,7 @@ You can also update the configured gas limit with these requests. |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/:voting_pubkey` | | Method | PATCH | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200, 400 | #### Example Path @@ -147,7 +147,7 @@ INFO Published validator registrations to the builder network, count: 3, service ### Fee Recipient -Refer to [suggested fee recipient](suggested-fee-recipient.md) documentation. +Refer to [suggested fee recipient](validator_fee_recipient.md) documentation. ### Validator definitions example @@ -244,16 +244,9 @@ INFO Builder payload ignored INFO Chain is unhealthy, using local payload ``` -In case of fallback you should see a log indicating that the locally produced payload was -used in place of one from the builder: - -```text -INFO Reconstructing a full block using a local payload -``` - ## Information for block builders and relays -Block builders and relays can query beacon node events from the [Events API](https://ethereum.github.io/beacon-APIs/#/Events/eventstream). An example of querying the payload attributes in the Events API is outlined in [Beacon node API - Events API](./api-bn.md#events-api) +Block builders and relays can query beacon node events from the [Events API](https://ethereum.github.io/beacon-APIs/#/Events/eventstream). An example of querying the payload attributes in the Events API is outlined in [Beacon node API - Events API](./api_bn.md#events-api) [mev-rs]: https://github.com/ralexstokes/mev-rs [mev-boost]: https://github.com/flashbots/mev-boost diff --git a/book/src/checkpoint-sync.md b/book/src/advanced_checkpoint_sync.md similarity index 99% rename from book/src/checkpoint-sync.md rename to book/src/advanced_checkpoint_sync.md index 8dd63f77c9..45aed6ef58 100644 --- a/book/src/checkpoint-sync.md +++ b/book/src/advanced_checkpoint_sync.md @@ -134,7 +134,7 @@ Important information to be aware of: * It is safe to interrupt state reconstruction by gracefully terminating the node – it will pick up from where it left off when it restarts. * You can start reconstruction from the HTTP API, and view its progress. See the - [`/lighthouse/database`](./api-lighthouse.md) APIs. + [`/lighthouse/database`](./api_lighthouse.md) APIs. For more information on historic state storage see the [Database Configuration](./advanced_database.md) page. diff --git a/book/src/advanced_database.md b/book/src/advanced_database.md index b558279730..4e77046c2d 100644 --- a/book/src/advanced_database.md +++ b/book/src/advanced_database.md @@ -61,6 +61,26 @@ that we have observed are: to apply. We observed no significant performance benefit from `--hierarchy-exponents 5,7,11`, and a substantial increase in space consumed. +The following table lists the data for different configurations. Note that the disk space requirement is for the `chain_db` and `freezer_db`, excluding the `blobs_db`. + +| Hierarchy Exponents | Storage Requirement | Sequential Slot Query | Uncached Query | Time to Sync | +|---|---|---|---|---| +| 5,9,11,13,16,18,21 (default) | 418 GiB | 250-700 ms | up to 10 s | 1 week | +| 5,7,11 (frequent snapshots) | 589 GiB | 250-700 ms | up to 6 s | 1 week | +| 0,5,7,11 (per-slot diffs) | 2500 GiB | 250-700 ms | up to 4 s | 7 weeks | + +[Jim](https://github.com/mcdee) has done some experiments to study the response time of querying random slots (uncached query) for `--hierarchy-exponents 0,5,7,11` (per-slot diffs) and `--hierarchy-exponents 5,9,11,13,17,21` (per-epoch diffs), as show in the figures below. From the figures, two points can be concluded: + +- response time (y-axis) increases with slot number (x-axis) due to state growth. +- response time for per-slot configuration in general is 2x faster than that of per-epoch. + +In short, setting different configurations is a trade-off between disk space requirement, sync time and response time. The data presented here is useful to help users choosing the configuration that suit their needs. + +_We acknowledge the data provided by [Jim](https://github.com/mcdee) and his consent for us to share it here._ + +![Response time for per-epoch archive](./imgs/per-epoch.png) +![Response time for per-slot archive](./imgs/per-slot.png) + If in doubt, we recommend running with the default configuration! It takes a long time to reconstruct states in any given configuration, so it might be some time before the optimal configuration is determined. diff --git a/book/src/database-migrations.md b/book/src/advanced_database_migrations.md similarity index 100% rename from book/src/database-migrations.md rename to book/src/advanced_database_migrations.md diff --git a/book/src/advanced-datadir.md b/book/src/advanced_datadir.md similarity index 98% rename from book/src/advanced-datadir.md rename to book/src/advanced_datadir.md index 7ad993a107..1be8ed5a34 100644 --- a/book/src/advanced-datadir.md +++ b/book/src/advanced_datadir.md @@ -12,7 +12,7 @@ lighthouse --network mainnet --datadir /var/lib/my-custom-dir bn --staking lighthouse --network mainnet --datadir /var/lib/my-custom-dir vc ``` -The first step creates a `validators` directory under `/var/lib/my-custom-dir` which contains the imported keys and [`validator_definitions.yml`](./validator-management.md). +The first step creates a `validators` directory under `/var/lib/my-custom-dir` which contains the imported keys and [`validator_definitions.yml`](./validator_management.md). After that, we simply run the beacon chain and validator client with the custom dir path. ## Relative Paths diff --git a/book/src/key-recovery.md b/book/src/advanced_key_recovery.md similarity index 100% rename from book/src/key-recovery.md rename to book/src/advanced_key_recovery.md diff --git a/book/src/advanced_networking.md b/book/src/advanced_networking.md index 0dc1000aa0..0dc53bd42a 100644 --- a/book/src/advanced_networking.md +++ b/book/src/advanced_networking.md @@ -123,8 +123,12 @@ Lighthouse listens for connections, and the parameters used to tell other peers how to connect to your node. This distinction is relevant and applies to most nodes that do not run directly on a public network. +Since Lighthouse v7.0.0, Lighthouse listens to both IPv4 and IPv6 by default if it detects a globally routable IPv6 address. This means that dual-stack is enabled by default. + ### Configuring Lighthouse to listen over IPv4/IPv6/Dual stack +To listen over only IPv4 and not IPv6, use the flag `--listen-address 0.0.0.0`. + To listen over only IPv6 use the same parameters as done when listening over IPv4 only: @@ -136,7 +140,7 @@ TCP and UDP. If the specified port is 9909, QUIC will use port 9910 for IPv6 UDP connections. This can be configured with `--quic-port`. -To listen over both IPv4 and IPv6: +To listen over both IPv4 and IPv6 and using a different port for IPv6:: - Set two listening addresses using the `--listen-address` flag twice ensuring the two addresses are one IPv4, and the other IPv6. When doing so, the @@ -165,7 +169,7 @@ To listen over both IPv4 and IPv6: > It listens on the default value of --port6 (`9000`) for both UDP and TCP. > QUIC will use port `9001` for UDP, which is the default `--port6` value (`9000`) + 1. -> When using `--listen-address :: --listen-address --port 9909 --discovery-port6 9999`, listening will be set up as follows: +> When using `--listen-address :: --listen-address 0.0.0.0 --port 9909 --discovery-port6 9999`, listening will be set up as follows: > > **IPv4**: > diff --git a/book/src/advanced-proposer-only.md b/book/src/advanced_proposer_only.md similarity index 97% rename from book/src/advanced-proposer-only.md rename to book/src/advanced_proposer_only.md index 1ea3610988..f55e51606c 100644 --- a/book/src/advanced-proposer-only.md +++ b/book/src/advanced_proposer_only.md @@ -56,7 +56,7 @@ these nodes for added security). The intended set-up to take advantage of this mechanism is to run one (or more) normal beacon nodes in conjunction with one (or more) proposer-only beacon -nodes. See the [Redundancy](./redundancy.md) section for more information about +nodes. See the [Redundancy](./advanced_redundancy.md) section for more information about setting up redundant beacon nodes. The proposer-only beacon nodes should be setup to use a different IP address than the primary (non proposer-only) nodes. For added security, the IP addresses of the proposer-only nodes should be diff --git a/book/src/late-block-re-orgs.md b/book/src/advanced_re-orgs.md similarity index 100% rename from book/src/late-block-re-orgs.md rename to book/src/advanced_re-orgs.md diff --git a/book/src/redundancy.md b/book/src/advanced_redundancy.md similarity index 94% rename from book/src/redundancy.md rename to book/src/advanced_redundancy.md index daf0eb4a5b..4582866657 100644 --- a/book/src/redundancy.md +++ b/book/src/advanced_redundancy.md @@ -9,7 +9,7 @@ There are three places in Lighthouse where redundancy is notable: We mention (3) since it is unsafe and should not be confused with the other two uses of redundancy. **Running the same validator keypair in more than one validator client (Lighthouse, or otherwise) will eventually lead to slashing.** -See [Slashing Protection](./slashing-protection.md) for more information. +See [Slashing Protection](./validator_slashing_protection.md) for more information. From this paragraph, this document will *only* refer to the first two items (1, 2). We *never* recommend that users implement redundancy for validator keypairs. @@ -58,8 +58,8 @@ following flags: > Note: You could also use `--http-address 0.0.0.0`, but this allows *any* external IP address to access the HTTP server. As such, a firewall should be configured to deny unauthorized access to port `5052`. -- `--execution-endpoint`: see [Merge Migration](./merge-migration.md). -- `--execution-jwt`: see [Merge Migration](./merge-migration.md). +- `--execution-endpoint`: see [Merge Migration](./archived_merge_migration.md). +- `--execution-jwt`: see [Merge Migration](./archived_merge_migration.md). For example one could use the following command to provide a backup beacon node: @@ -107,7 +107,7 @@ The default is `--broadcast subscriptions`. To also broadcast blocks for example Lighthouse previously supported redundant execution nodes for fetching data from the deposit contract. On merged networks *this is no longer supported*. Each Lighthouse beacon node must be configured in a 1:1 relationship with an execution node. For more information on the rationale -behind this decision please see the [Merge Migration](./merge-migration.md) documentation. +behind this decision please see the [Merge Migration](./archived_merge_migration.md) documentation. To achieve redundancy we recommend configuring [Redundant beacon nodes](#redundant-beacon-nodes) where each has its own execution engine. diff --git a/book/src/advanced-release-candidates.md b/book/src/advanced_release_candidates.md similarity index 100% rename from book/src/advanced-release-candidates.md rename to book/src/advanced_release_candidates.md diff --git a/book/src/slasher.md b/book/src/advanced_slasher.md similarity index 99% rename from book/src/slasher.md rename to book/src/advanced_slasher.md index 3310f6c9ef..b354c9deb2 100644 --- a/book/src/slasher.md +++ b/book/src/advanced_slasher.md @@ -81,7 +81,7 @@ WARN Slasher backend override failed advice: delete old MDBX database or enab In this case you should either obtain a Lighthouse binary with the MDBX backend enabled, or delete the files for the old backend. The pre-built Lighthouse binaries and Docker images have MDBX enabled, -or if you're [building from source](./installation-source.md) you can enable the `slasher-mdbx` feature. +or if you're [building from source](./installation_source.md) you can enable the `slasher-mdbx` feature. To delete the files, use the `path` from the `WARN` log, and then delete the `mbdx.dat` and `mdbx.lck` files. diff --git a/book/src/validator-web3signer.md b/book/src/advanced_web3signer.md similarity index 95% rename from book/src/validator-web3signer.md rename to book/src/advanced_web3signer.md index 6a518af3cf..6145fd4a71 100644 --- a/book/src/validator-web3signer.md +++ b/book/src/advanced_web3signer.md @@ -30,7 +30,7 @@ or effectiveness. ## Usage A remote signing validator is added to Lighthouse in much the same way as one that uses a local -keystore, via the [`validator_definitions.yml`](./validator-management.md) file or via the [`POST /lighthouse/validators/web3signer`](./api-vc-endpoints.md#post-lighthousevalidatorsweb3signer) API endpoint. +keystore, via the [`validator_definitions.yml`](./validator_management.md) file or via the [`POST /lighthouse/validators/web3signer`](./api_vc_endpoints.md#post-lighthousevalidatorsweb3signer) API endpoint. Here is an example of a `validator_definitions.yml` file containing one validator which uses a remote signer: diff --git a/book/src/api.md b/book/src/api.md index 5837ad9654..912c8658b6 100644 --- a/book/src/api.md +++ b/book/src/api.md @@ -5,5 +5,5 @@ RESTful HTTP/JSON APIs. There are two APIs served by Lighthouse: -- [Beacon Node API](./api-bn.md) -- [Validator Client API](./api-vc.md) +- [Beacon Node API](./api_bn.md) +- [Validator Client API](./api_vc.md) diff --git a/book/src/api-bn.md b/book/src/api_bn.md similarity index 100% rename from book/src/api-bn.md rename to book/src/api_bn.md diff --git a/book/src/api-lighthouse.md b/book/src/api_lighthouse.md similarity index 98% rename from book/src/api-lighthouse.md rename to book/src/api_lighthouse.md index 5428ab8f9a..b65bef4762 100644 --- a/book/src/api-lighthouse.md +++ b/book/src/api_lighthouse.md @@ -347,11 +347,11 @@ curl -X GET "http://localhost:5052/lighthouse/proto_array" -H "accept: applicat ## `/lighthouse/validator_inclusion/{epoch}/{validator_id}` -See [Validator Inclusion APIs](./validator-inclusion.md). +See [Validator Inclusion APIs](./api_validator_inclusion.md). ## `/lighthouse/validator_inclusion/{epoch}/global` -See [Validator Inclusion APIs](./validator-inclusion.md). +See [Validator Inclusion APIs](./api_validator_inclusion.md). ## `/lighthouse/eth1/syncing` @@ -565,7 +565,7 @@ For archive nodes, the `anchor` will be: indicating that all states with slots `>= 0` are available, i.e., full state history. For more information on the specific meanings of these fields see the docs on [Checkpoint -Sync](./checkpoint-sync.md#reconstructing-states). +Sync](./advanced_checkpoint_sync.md#reconstructing-states). ## `/lighthouse/merge_readiness` @@ -812,9 +812,15 @@ Checks if the ports are open. curl -X GET "http://localhost:5052/lighthouse/nat" | jq ``` -An open port will return: +An example of response: ```json { - "data": true + "data": { + "discv5_ipv4": true, + "discv5_ipv6": false, + "libp2p_ipv4": true, + "libp2p_ipv6": false + } } +``` diff --git a/book/src/advanced_metrics.md b/book/src/api_metrics.md similarity index 97% rename from book/src/advanced_metrics.md rename to book/src/api_metrics.md index 323ba8f58a..c124d3acb7 100644 --- a/book/src/advanced_metrics.md +++ b/book/src/api_metrics.md @@ -68,7 +68,7 @@ The specification for the monitoring endpoint can be found here: - -_Note: the similarly named [Validator Monitor](./validator-monitoring.md) feature is entirely +_Note: the similarly named [Validator Monitor](./validator_monitoring.md) feature is entirely independent of remote metric monitoring_. ### Update Period diff --git a/book/src/validator-inclusion.md b/book/src/api_validator_inclusion.md similarity index 100% rename from book/src/validator-inclusion.md rename to book/src/api_validator_inclusion.md diff --git a/book/src/api-vc.md b/book/src/api_vc.md similarity index 91% rename from book/src/api-vc.md rename to book/src/api_vc.md index 630a032006..f5df5df76c 100644 --- a/book/src/api-vc.md +++ b/book/src/api_vc.md @@ -6,11 +6,10 @@ of validators and keys. The API includes all of the endpoints from the [standard keymanager API](https://ethereum.github.io/keymanager-APIs/) that is implemented by other clients and remote signers. It also includes some Lighthouse-specific endpoints which are described in -[Endpoints](./api-vc-endpoints.md). +[Endpoints](./api_vc_endpoints.md). > Note: All requests to the HTTP server must supply an -> [`Authorization`](./api-vc-auth-header.md) header. All responses contain a -> [`Signature`](./api-vc-sig-header.md) header for optional verification. +> [`Authorization`](./api_vc_auth_header.md) header. ## Starting the server diff --git a/book/src/api-vc-auth-header.md b/book/src/api_vc_auth_header.md similarity index 100% rename from book/src/api-vc-auth-header.md rename to book/src/api_vc_auth_header.md diff --git a/book/src/api-vc-endpoints.md b/book/src/api_vc_endpoints.md similarity index 97% rename from book/src/api-vc-endpoints.md rename to book/src/api_vc_endpoints.md index 98605a3dcd..a7c6f0ad5e 100644 --- a/book/src/api-vc-endpoints.md +++ b/book/src/api_vc_endpoints.md @@ -19,7 +19,7 @@ | [`POST /lighthouse/validators/web3signer`](#post-lighthousevalidatorsweb3signer) | Add web3signer validators. | | [`GET /lighthouse/logs`](#get-lighthouselogs) | Get logs | -The query to Lighthouse API endpoints requires authorization, see [Authorization Header](./api-vc-auth-header.md). +The query to Lighthouse API endpoints requires authorization, see [Authorization Header](./api_vc_auth_header.md). In addition to the above endpoints Lighthouse also supports all of the [standard keymanager APIs](https://ethereum.github.io/keymanager-APIs/). @@ -33,7 +33,7 @@ Returns the software version and `git` commit hash for the Lighthouse binary. |-------------------|--------------------------------------------| | Path | `/lighthouse/version` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | Command: @@ -71,7 +71,7 @@ Returns information regarding the health of the host machine. |-------------------|--------------------------------------------| | Path | `/lighthouse/health` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | *Note: this endpoint is presently only available on Linux.* @@ -132,7 +132,7 @@ Returns information regarding the health of the host machine. |-------------------|--------------------------------------------| | Path | `/lighthouse/ui/health` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | Command: @@ -178,7 +178,7 @@ Returns the graffiti that will be used for the next block proposal of each valid |-------------------|--------------------------------------------| | Path | `/lighthouse/ui/graffiti` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | Command: @@ -210,7 +210,7 @@ Returns the Ethereum proof-of-stake consensus specification loaded for this vali |-------------------|--------------------------------------------| | Path | `/lighthouse/spec` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | Command: @@ -326,7 +326,7 @@ Example Response Body ## `GET /lighthouse/auth` -Fetch the filesystem path of the [authorization token](./api-vc-auth-header.md). +Fetch the filesystem path of the [authorization token](./api_vc_auth_header.md). Unlike the other endpoints this may be called *without* providing an authorization token. This API is intended to be called from the same machine as the validator client, so that the token @@ -365,7 +365,7 @@ Lists all validators managed by this validator client. |-------------------|--------------------------------------------| | Path | `/lighthouse/validators` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | Command: @@ -409,7 +409,7 @@ Get a validator by their `voting_pubkey`. |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/:voting_pubkey` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200, 400 | Command: @@ -441,7 +441,7 @@ and `graffiti`. The following example updates a validator from `enabled: true` |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/:voting_pubkey` | | Method | PATCH | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200, 400 | Example Request Body @@ -491,7 +491,7 @@ Validators are generated from the mnemonic according to |-------------------|--------------------------------------------| | Path | `/lighthouse/validators` | | Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | ### Example Request Body @@ -580,7 +580,7 @@ Import a keystore into the validator client. |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/keystore` | | Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | ### Example Request Body @@ -676,7 +676,7 @@ generated with the path `m/12381/3600/i/42`. |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/mnemonic` | | Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200 | ### Example Request Body @@ -739,7 +739,7 @@ Create any number of new validators, all of which will refer to a |-------------------|--------------------------------------------| | Path | `/lighthouse/validators/web3signer` | | Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200, 400 | ### Example Request Body diff --git a/book/src/key-management.md b/book/src/archived-key-management.md similarity index 98% rename from book/src/key-management.md rename to book/src/archived-key-management.md index fa6e99a2aa..3f600794e0 100644 --- a/book/src/key-management.md +++ b/book/src/archived-key-management.md @@ -1,4 +1,4 @@ -# Key Management (Deprecated) +# Key Management [launchpad]: https://launchpad.ethereum.org/ @@ -22,7 +22,7 @@ Rather than continuing to read this page, we recommend users visit either: - The [Staking Launchpad][launchpad] for detailed, beginner-friendly instructions. - The [staking-deposit-cli](https://github.com/ethereum/staking-deposit-cli) for a CLI tool used by the [Staking Launchpad][launchpad]. -- The [validator-manager documentation](./validator-manager.md) for a Lighthouse-specific tool for streamlined validator management tools. +- The [validator-manager documentation](./validator_manager.md) for a Lighthouse-specific tool for streamlined validator management tools. ## The `lighthouse account-manager` diff --git a/book/src/merge-migration.md b/book/src/archived-merge-migration.md similarity index 99% rename from book/src/merge-migration.md rename to book/src/archived-merge-migration.md index 7a123254bf..ac9c78c5e3 100644 --- a/book/src/merge-migration.md +++ b/book/src/archived-merge-migration.md @@ -14,7 +14,7 @@ the merge: 2. If your Lighthouse node has validators attached you *must* nominate an Ethereum address to receive transactions tips from blocks proposed by your validators. These changes should be made to your `lighthouse vc` configuration, and are covered on the - [Suggested fee recipient](./suggested-fee-recipient.md) page. + [Suggested fee recipient](./validator_fee_recipient.md) page. Additionally, you *must* update Lighthouse to v3.0.0 (or later), and must update your execution engine to a merge-ready version. diff --git a/book/src/archived.md b/book/src/archived.md new file mode 100644 index 0000000000..7b6e4b7e8e --- /dev/null +++ b/book/src/archived.md @@ -0,0 +1,3 @@ +# Archived + +This section keeps the topics that are deprecated or less applicable for archived purposes. diff --git a/book/src/pi.md b/book/src/archived_pi.md similarity index 91% rename from book/src/pi.md rename to book/src/archived_pi.md index b91ecab548..6afbcebd66 100644 --- a/book/src/pi.md +++ b/book/src/archived_pi.md @@ -7,7 +7,7 @@ Tested on: - Raspberry Pi 4 Model B (4GB) - `Ubuntu 20.04 LTS (GNU/Linux 5.4.0-1011-raspi aarch64)` -*Note: [Lighthouse supports cross-compiling](./cross-compiling.md) to target a +*Note: [Lighthouse supports cross-compiling](./installation_cross_compiling.md) to target a Raspberry Pi (`aarch64`). Compiling on a faster machine (i.e., `x86_64` desktop) may be convenient.* @@ -58,7 +58,7 @@ make > > Compiling Lighthouse can take up to an hour. The safety guarantees provided by the Rust language unfortunately result in a lengthy compilation time on a low-spec CPU like a Raspberry Pi. For faster -compilation on low-spec hardware, try [cross-compiling](./cross-compiling.md) on a more powerful +compilation on low-spec hardware, try [cross-compiling](./installation_cross_compiling.md) on a more powerful computer (e.g., compile for RasPi from your desktop computer). Once installation has finished, confirm Lighthouse is installed by viewing the diff --git a/book/src/contributing.md b/book/src/contributing.md index 312acccbc0..332afbfd70 100644 --- a/book/src/contributing.md +++ b/book/src/contributing.md @@ -15,7 +15,7 @@ to work on. To start contributing, 1. Read our [how to contribute](https://github.com/sigp/lighthouse/blob/unstable/CONTRIBUTING.md) document. -2. Setup a [development environment](./setup.md). +2. Setup a [development environment](./contributing_setup.md). 3. Browse through the [open issues](https://github.com/sigp/lighthouse/issues) (tip: look for the [good first issue](https://github.com/sigp/lighthouse/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) @@ -127,5 +127,5 @@ suggest: - [Rust by example](https://doc.rust-lang.org/stable/rust-by-example/) - [Learning Rust With Entirely Too Many Linked Lists](http://cglab.ca/~abeinges/blah/too-many-lists/book/) - [Rustlings](https://github.com/rustlings/rustlings) -- [Rust Exercism](https://exercism.io/tracks/rust) +- [Rust Exercism](https://exercism.org/tracks/rust) - [Learn X in Y minutes - Rust](https://learnxinyminutes.com/docs/rust/) diff --git a/book/src/setup.md b/book/src/contributing_setup.md similarity index 97% rename from book/src/setup.md rename to book/src/contributing_setup.md index d3da68f97c..7143c8f0fb 100644 --- a/book/src/setup.md +++ b/book/src/contributing_setup.md @@ -17,9 +17,6 @@ The additional requirements for developers are: some dependencies. See [`Installation Guide`](./installation.md) for more info. - [`java 17 runtime`](https://openjdk.java.net/projects/jdk/). 17 is the minimum, used by web3signer_tests. -- [`libpq-dev`](https://www.postgresql.org/docs/devel/libpq.html). Also known as - `libpq-devel` on some systems. -- [`docker`](https://www.docker.com/). Some tests need docker installed and **running**. ## Using `make` diff --git a/book/src/faq.md b/book/src/faq.md index d23951c8c7..a741834501 100644 --- a/book/src/faq.md +++ b/book/src/faq.md @@ -146,7 +146,7 @@ An example of the full log is shown below: WARN BlockProcessingFailure outcome: MissingBeaconBlock(0xbdba211f8d72029554e405d8e4906690dca807d1d7b1bc8c9b88d7970f1648bc), msg: unexpected condition in processing block. ``` -`MissingBeaconBlock` suggests that the database has corrupted. You should wipe the database and use [Checkpoint Sync](./checkpoint-sync.md) to resync the beacon chain. +`MissingBeaconBlock` suggests that the database has corrupted. You should wipe the database and use [Checkpoint Sync](./advanced_checkpoint_sync.md) to resync the beacon chain. ### After checkpoint sync, the progress of `downloading historical blocks` is slow. Why? @@ -281,7 +281,7 @@ You should **never** use duplicate/redundant validator keypairs or validator cli duplicate your JSON keystores and don't run `lighthouse vc` twice). This will lead to slashing. However, there are some components which can be configured with redundancy. See the -[Redundancy](./redundancy.md) guide for more information. +[Redundancy](./advanced_redundancy.md) guide for more information. ### I am missing attestations. Why? @@ -323,7 +323,7 @@ Another possible reason for missing the head vote is due to a chain "reorg". A r ### Can I submit a voluntary exit message without running a beacon node? -Yes. Beaconcha.in provides the tool to broadcast the message. You can create the voluntary exit message file with [ethdo](https://github.com/wealdtech/ethdo/releases/tag/v1.30.0) and submit the message via the [beaconcha.in](https://beaconcha.in/tools/broadcast) website. A guide on how to use `ethdo` to perform voluntary exit can be found [here](https://github.com/eth-educators/ethstaker-guides/blob/main/voluntary-exit.md). +Yes. Beaconcha.in provides the tool to broadcast the message. You can create the voluntary exit message file with [ethdo](https://github.com/wealdtech/ethdo/releases/tag/v1.30.0) and submit the message via the [beaconcha.in](https://beaconcha.in/tools/broadcast) website. A guide on how to use `ethdo` to perform voluntary exit can be found [here](https://github.com/eth-educators/ethstaker-guides/blob/main/docs/voluntary-exit.md). It is also noted that you can submit your BLS-to-execution-change message to update your withdrawal credentials from type `0x00` to `0x01` using the same link. @@ -341,13 +341,13 @@ No. You can just import new validator keys to the destination directory. If the Generally yes. -If you do not want to stop `lighthouse vc`, you can use the [key manager API](./api-vc-endpoints.md) to import keys. +If you do not want to stop `lighthouse vc`, you can use the [key manager API](./api_vc_endpoints.md) to import keys. ### How can I delete my validator once it is imported? Lighthouse supports the [KeyManager API](https://ethereum.github.io/keymanager-APIs/#/Local%20Key%20Manager/deleteKeys) to delete validators and remove them from the `validator_definitions.yml` file. To do so, start the validator client with the flag `--http` and call the API. -If you are looking to delete the validators in one node and import it to another, you can use the [validator-manager](./validator-manager-move.md) to move the validators across nodes without the hassle of deleting and importing the keys. +If you are looking to delete the validators in one node and import it to another, you can use the [validator-manager](./validator_manager_move.md) to move the validators across nodes without the hassle of deleting and importing the keys. ## Network, Monitoring and Maintenance @@ -389,9 +389,9 @@ expect, there are a few things to check on: ### How do I update lighthouse? -If you are updating to new release binaries, it will be the same process as described [here.](./installation-binaries.md) +If you are updating to new release binaries, it will be the same process as described [here.](./installation_binaries.md) -If you are updating by rebuilding from source, see [here.](./installation-source.md#update-lighthouse) +If you are updating by rebuilding from source, see [here.](./installation_source.md#update-lighthouse) If you are running the docker image provided by Sigma Prime on Dockerhub, you can update to specific versions, for example: @@ -399,7 +399,7 @@ If you are running the docker image provided by Sigma Prime on Dockerhub, you ca docker pull sigp/lighthouse:v1.0.0 ``` -If you are building a docker image, the process will be similar to the one described [here.](./docker.md#building-the-docker-image) +If you are building a docker image, the process will be similar to the one described [here.](./installation_docker.md#building-the-docker-image) You just need to make sure the code you have checked out is up to date. ### Do I need to set up any port mappings (port forwarding)? @@ -436,7 +436,7 @@ Opening these ports will make your Lighthouse node maximally contactable. Apart from using block explorers, you may use the "Validator Monitor" built into Lighthouse which provides logging and Prometheus/Grafana metrics for individual validators. See [Validator -Monitoring](./validator-monitoring.md) for more information. Lighthouse has also developed Lighthouse UI (Siren) to monitor performance, see [Lighthouse UI (Siren)](./lighthouse-ui.md). +Monitoring](./validator_monitoring.md) for more information. Lighthouse has also developed Lighthouse UI (Siren) to monitor performance, see [Lighthouse UI (Siren)](./ui.md). ### My beacon node and validator client are on different servers. How can I point the validator client to the beacon node? @@ -454,7 +454,7 @@ The setting on the beacon node is the same for both cases below. In the beacon n curl "http://local_IP:5052/eth/v1/node/version" ``` - You can refer to [Redundancy](./redundancy.md) for more information. + You can refer to [Redundancy](./advanced_redundancy.md) for more information. 2. If the beacon node and validator clients are on different servers _and different networks_, it is necessary to perform port forwarding of the SSH port (e.g., the default port 22) on the router, and also allow firewall on the SSH port. The connection can be established via port forwarding on the router. @@ -514,11 +514,11 @@ which shows that there are a total of 36 peers connected via QUIC. ### What should I do if I lose my slashing protection database? -See [here](./slashing-protection.md#misplaced-slashing-database). +See [here](./validator_slashing_protection.md#misplaced-slashing-database). ### I can't compile lighthouse -See [here.](./installation-source.md#troubleshooting) +See [here.](./installation_source.md#troubleshooting) ### How do I check the version of Lighthouse that is running? @@ -550,7 +550,7 @@ which says that the version is v4.1.0. ### Does Lighthouse have pruning function like the execution client to save disk space? -Yes, Lighthouse supports [state pruning](./database-migrations.md#how-to-prune-historic-states) which can help to save disk space. +Yes, Lighthouse supports [state pruning](./advanced_database_migrations.md#how-to-prune-historic-states) which can help to save disk space. ### Can I use a HDD for the freezer database and only have the hot db on SSD? diff --git a/book/src/help_bn.md b/book/src/help_bn.md index cbcb1ec5a3..224db099e7 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -28,6 +28,8 @@ Options: network. Multiaddr is also supported. --builder The URL of a service compatible with the MEV-boost API. + --builder-disable-ssz + Disables sending requests using SSZ over the builder API. --builder-fallback-epochs-since-finalization If this node is proposing a block and the chain has not finalized within this number of epochs, it will NOT query any connected @@ -76,8 +78,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --discovery-port The UDP port that discovery will listen on. Defaults to `port` --discovery-port6 @@ -116,7 +117,7 @@ Options: --epochs-per-blob-prune The epoch interval with which to prune blobs from Lighthouse's database when they are older than the data availability boundary - relative to the current epoch. [default: 1] + relative to the current epoch. [default: 256] --epochs-per-migration The number of epochs to wait between running the migration of data from the hot DB to the cold DB. Less frequent runs can be useful for @@ -243,15 +244,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -288,7 +285,7 @@ Options: monitoring-endpoint. Default: 60s --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] --network-dir Data directory for network keys. Defaults to network/ inside the beacon node dir. @@ -383,8 +380,11 @@ Options: Number of validators per chunk stored on disk. --slots-per-restore-point DEPRECATED. This flag has no effect. + --state-cache-headroom + Minimum number of states to cull from the state cache when it gets + full [default: 1] --state-cache-size - Specifies the size of the state cache [default: 128] + Specifies the size of the state cache [default: 32] --suggested-fee-recipient Emergency fallback fee recipient for use in case the validator client does not have one configured. You should set this flag on the @@ -515,8 +515,13 @@ Flags: all attestations are received for import. --light-client-server DEPRECATED - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_general.md b/book/src/help_general.md index 996b048d10..4de3270cfc 100644 --- a/book/src/help_general.md +++ b/book/src/help_general.md @@ -42,8 +42,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --genesis-state-url A URL of a beacon-API compatible server from which to download the genesis state. Checkpoint sync server URLs can generally be used with @@ -56,15 +55,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -76,7 +71,7 @@ Options: set to 0, background file logging is disabled. [default: 200] --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] -t, --testnet-dir Path to directory containing eth2_testnet specs. Defaults to a hard-coded Lighthouse testnet. Only effective if there is no existing @@ -93,8 +88,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vc.md b/book/src/help_vc.md index 948a09f44d..e01828aea2 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -35,8 +35,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --gas-limit The gas limit to be used in all builder proposals for all validators managed by this validator client. Note this will not necessarily be @@ -77,15 +76,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -119,7 +114,7 @@ Options: monitoring-endpoint. [default: 60] --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] --proposer-nodes Comma-separated addresses to one or more beacon node HTTP APIs. These specify nodes that are used to send beacon block proposals. A failure @@ -175,6 +170,10 @@ Flags: If this flag is set, Lighthouse will query the Beacon Node for only block headers during proposals and will sign over headers. Useful for outsourcing execution payload construction during proposals. + --disable-attesting + Disable the performance of attestation duties (and sync committee + duties). This flag should only be used in emergencies to prioritise + block proposal duties. --disable-auto-discover If present, do not attempt to discover new validators in the validators-dir. Validators will need to be manually added to the @@ -236,8 +235,13 @@ Flags: database will have been initialized when you imported your validator keys. If you misplace your database and then run with this flag you risk being slashed. - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. @@ -247,6 +251,13 @@ Flags: contain sensitive information about your validator and so this flag should be used with caution. For Windows users, the log file permissions will be inherited from the parent folder. + --long-timeouts-multiplier + If present, the validator client will use a multiplier for the timeout + when making requests to the beacon node. This only takes effect when + the `--use-long-timeouts` flag is present. The timeouts will be the + slot duration multiplied by this value. This flag is generally not + recommended, longer timeouts can cause missed duties when fallbacks + are used. [default: 1] --metrics Enable the Prometheus metrics HTTP server. Disabled by default. --prefer-builder-proposals diff --git a/book/src/help_vm.md b/book/src/help_vm.md index 50c204f371..fdb035aa05 100644 --- a/book/src/help_vm.md +++ b/book/src/help_vm.md @@ -39,8 +39,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --genesis-state-url A URL of a beacon-API compatible server from which to download the genesis state. Checkpoint sync server URLs can generally be used with @@ -53,15 +52,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -73,7 +68,7 @@ Options: set to 0, background file logging is disabled. [default: 200] --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] -t, --testnet-dir Path to directory containing eth2_testnet specs. Defaults to a hard-coded Lighthouse testnet. Only effective if there is no existing @@ -88,8 +83,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_create.md b/book/src/help_vm_create.md index 2743117eae..c6bfe2e95a 100644 --- a/book/src/help_vm_create.md +++ b/book/src/help_vm_create.md @@ -33,8 +33,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --deposit-gwei The GWEI value of the deposit amount. Defaults to the minimum amount required for an active validator (MAX_EFFECTIVE_BALANCE) @@ -60,15 +59,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -82,7 +77,7 @@ Options: If present, the mnemonic will be read in from this file. --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] --output-path The path to a directory where the validator and (optionally) deposits files will be created. The directory will be created if it does not @@ -118,8 +113,13 @@ Flags: address. This is not recommended. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_import.md b/book/src/help_vm_import.md index 68aab768ae..8da3eee9b0 100644 --- a/book/src/help_vm_import.md +++ b/book/src/help_vm_import.md @@ -23,8 +23,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --gas-limit When provided, the imported validator will use this gas limit. It is recommended to leave this as the default value by not specifying this @@ -45,15 +44,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -65,7 +60,7 @@ Options: set to 0, background file logging is disabled. [default: 200] --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] --password Password of the keystore file. --prefer-builder-proposals @@ -104,8 +99,13 @@ Flags: directly cause slashable conditions, it might be an indicator that something is amiss. Users should also be careful to avoid submitting duplicate deposits for validators that already exist on the VC. - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_move.md b/book/src/help_vm_move.md index 99eee32c78..83824ce768 100644 --- a/book/src/help_vm_move.md +++ b/book/src/help_vm_move.md @@ -25,8 +25,7 @@ Options: custom datadirs for different networks. --debug-level Specifies the verbosity level used when emitting logs to the terminal. - [default: info] [possible values: info, debug, trace, warn, error, - crit] + [default: info] [possible values: info, debug, trace, warn, error] --dest-vc-token The file containing a token required by the destination validator client. @@ -49,15 +48,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: - debug] [possible values: info, debug, trace, warn, error, crit] + debug] [possible values: info, debug, trace, warn, error] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -69,7 +64,7 @@ Options: set to 0, background file logging is disabled. [default: 200] --network Name of the Eth2 chain Lighthouse will sync and follow. [possible - values: mainnet, gnosis, chiado, sepolia, holesky] + values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] --prefer-builder-proposals If this flag is set, Lighthouse will always prefer blocks constructed by builders, regardless of payload value. [possible values: true, @@ -100,8 +95,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/imgs/per-epoch.png b/book/src/imgs/per-epoch.png new file mode 100644 index 0000000000..d4ac77ecbb Binary files /dev/null and b/book/src/imgs/per-epoch.png differ diff --git a/book/src/imgs/per-slot.png b/book/src/imgs/per-slot.png new file mode 100644 index 0000000000..91b9c12e4c Binary files /dev/null and b/book/src/imgs/per-slot.png differ diff --git a/book/src/installation.md b/book/src/installation.md index 137a00b918..95550e0807 100644 --- a/book/src/installation.md +++ b/book/src/installation.md @@ -4,18 +4,18 @@ Lighthouse runs on Linux, macOS, and Windows. There are three core methods to obtain the Lighthouse application: -- [Pre-built binaries](./installation-binaries.md). -- [Docker images](./docker.md). -- [Building from source](./installation-source.md). +- [Pre-built binaries](./installation_binaries.md). +- [Docker images](./installation_docker.md). +- [Building from source](./installation_source.md). Additionally, there are two extra guides for specific uses: -- [Raspberry Pi 4 guide](./pi.md). (Archived) -- [Cross-compiling guide for developers](./cross-compiling.md). +- [Raspberry Pi 4 guide](./archived_pi.md). (Archived) +- [Cross-compiling guide for developers](./installation_cross_compiling.md). There are also community-maintained installation methods: -- [Homebrew package](./homebrew.md). +- [Homebrew package](./installation_homebrew.md). - Arch Linux AUR packages: [source](https://aur.archlinux.org/packages/lighthouse-ethereum), [binary](https://aur.archlinux.org/packages/lighthouse-ethereum-bin). diff --git a/book/src/installation-binaries.md b/book/src/installation_binaries.md similarity index 100% rename from book/src/installation-binaries.md rename to book/src/installation_binaries.md diff --git a/book/src/cross-compiling.md b/book/src/installation_cross_compiling.md similarity index 90% rename from book/src/cross-compiling.md rename to book/src/installation_cross_compiling.md index c90001d561..4f6ba9af38 100644 --- a/book/src/cross-compiling.md +++ b/book/src/installation_cross_compiling.md @@ -34,10 +34,10 @@ in `lighthouse/target/aarch64-unknown-linux-gnu/release`. When using the makefile the set of features used for building can be controlled with the environment variable `CROSS_FEATURES`. See [Feature - Flags](./installation-source.md#feature-flags) for available features. + Flags](./installation_source.md#feature-flags) for available features. ## Compilation Profiles When using the makefile the build profile can be controlled with the environment variable -`CROSS_PROFILE`. See [Compilation Profiles](./installation-source.md#compilation-profiles) for +`CROSS_PROFILE`. See [Compilation Profiles](./installation_source.md#compilation-profiles) for available profiles. diff --git a/book/src/docker.md b/book/src/installation_docker.md similarity index 100% rename from book/src/docker.md rename to book/src/installation_docker.md diff --git a/book/src/homebrew.md b/book/src/installation_homebrew.md similarity index 100% rename from book/src/homebrew.md rename to book/src/installation_homebrew.md diff --git a/book/src/installation-priorities.md b/book/src/installation_priorities.md similarity index 100% rename from book/src/installation-priorities.md rename to book/src/installation_priorities.md diff --git a/book/src/installation-source.md b/book/src/installation_source.md similarity index 95% rename from book/src/installation-source.md rename to book/src/installation_source.md index 19098a5bc8..0aa8a99a5e 100644 --- a/book/src/installation-source.md +++ b/book/src/installation_source.md @@ -23,6 +23,8 @@ The rustup installer provides an easy way to update the Rust compiler, and works With Rust installed, follow the instructions below to install dependencies relevant to your operating system. +> Note: For Linux OS, general Linux File Systems such as Ext4 or XFS are fine. We recommend to avoid using Btrfs file system as it has been reported to be slow and the node will suffer from performance degradation as a result. + ### Ubuntu Install the following packages: @@ -216,7 +218,7 @@ Rust Version (MSRV) which is listed under the `rust-version` key in Lighthouse's If compilation fails with `(signal: 9, SIGKILL: kill)`, this could mean your machine ran out of memory during compilation. If you are on a resource-constrained device you can -look into [cross compilation](./cross-compiling.md), or use a [pre-built +look into [cross compilation](./installation_cross_compiling.md), or use a [pre-built binary](https://github.com/sigp/lighthouse/releases). If compilation fails with `error: linking with cc failed: exit code: 1`, try running `cargo clean`. diff --git a/book/src/intro.md b/book/src/intro.md index 9892a8a49d..e572904685 100644 --- a/book/src/intro.md +++ b/book/src/intro.md @@ -19,9 +19,9 @@ You may read this book from start to finish, or jump to some of these topics: - Follow the [Installation Guide](./installation.md) to install Lighthouse. - Run your very [own beacon node](./run_a_node.md). -- Learn about [becoming a mainnet validator](./mainnet-validator.md). -- Get hacking with the [Development Environment Guide](./setup.md). -- Utilize the whole stack by starting a [local testnet](./setup.md#local-testnets). +- Learn about [becoming a mainnet validator](./mainnet_validator.md). +- Get hacking with the [Development Environment Guide](./contributing_setup.md). +- Utilize the whole stack by starting a [local testnet](./contributing_setup.md#local-testnets). - Query the [RESTful HTTP API](./api.md) using `curl`. Prospective contributors can read the [Contributing](./contributing.md) section diff --git a/book/src/mainnet-validator.md b/book/src/mainnet_validator.md similarity index 96% rename from book/src/mainnet-validator.md rename to book/src/mainnet_validator.md index c53be97ccf..d21d49f0c9 100644 --- a/book/src/mainnet-validator.md +++ b/book/src/mainnet_validator.md @@ -1,9 +1,9 @@ # Become an Ethereum Consensus Mainnet Validator [launchpad]: https://launchpad.ethereum.org/ -[advanced-datadir]: ./advanced-datadir.md +[advanced-datadir]: ./advanced_datadir.md [license]: https://github.com/sigp/lighthouse/blob/stable/LICENSE -[slashing]: ./slashing-protection.md +[slashing]: ./validator_slashing_protection.md [discord]: https://discord.gg/cyAszAh Becoming an Ethereum consensus validator is rewarding, but it's not for the faint of heart. You'll need to be @@ -54,7 +54,7 @@ and follow the instructions to generate the keys. When prompted for a network, s Upon completing this step, the files `deposit_data-*.json` and `keystore-m_*.json` will be created. The keys that are generated from staking-deposit-cli can be easily loaded into a Lighthouse validator client (`lighthouse vc`) in [Step 3](#step-3-import-validator-keys-to-lighthouse). In fact, both of these programs are designed to work with each other. -> Lighthouse also supports creating validator keys, see [Key management](./key-management.md) for more info. +> Lighthouse also supports creating validator keys, see [Validator Manager Create](./validator_manager_create.md) for more info. ### Step 2. Start an execution client and Lighthouse beacon node @@ -99,7 +99,7 @@ Enter the keystore password, or press enter to omit it: ``` The user can choose whether or not they'd like to store the validator password -in the [`validator_definitions.yml`](./validator-management.md) file. If the +in the [`validator_definitions.yml`](./validator_management.md) file. If the password is *not* stored here, the validator client (`lighthouse vc`) application will ask for the password each time it starts. This might be nice for some users from a security perspective (i.e., if it is a shared computer), @@ -179,7 +179,7 @@ After the validator is running and performing its duties, it is important to kee The next important thing is to stay up to date with updates to Lighthouse and the execution client. Updates are released from time to time, typically once or twice a month. For Lighthouse updates, you can subscribe to notifications on [Github](https://github.com/sigp/lighthouse) by clicking on `Watch`. If you only want to receive notification on new releases, select `Custom`, then `Releases`. You could also join [Lighthouse Discord](https://discord.gg/cyAszAh) where we will make an announcement when there is a new release. -You may also want to try out [Siren](./lighthouse-ui.md), a UI developed by Lighthouse to monitor validator performance. +You may also want to try out [Siren](./ui.md), a UI developed by Lighthouse to monitor validator performance. Once you are familiar with running a validator and server maintenance, you'll find that running Lighthouse is easy. Install it, start it, monitor it and keep it updated. You shouldn't need to interact with it on a day-to-day basis. Happy staking! diff --git a/book/src/run_a_node.md b/book/src/run_a_node.md index 9b9e0cba8e..15567497e5 100644 --- a/book/src/run_a_node.md +++ b/book/src/run_a_node.md @@ -129,7 +129,7 @@ INFO Downloading historical blocks est_time: 5 hrs 0 mins, speed: 111.96 slots/ Once backfill is complete, a `INFO Historical block download complete` log will be emitted. -Check out the [FAQ](./checkpoint-sync.md#faq) for more information on checkpoint sync. +Check out the [FAQ](./advanced_checkpoint_sync.md#faq) for more information on checkpoint sync. ### Logs - Syncing @@ -146,11 +146,10 @@ Once you see the above message - congratulations! This means that your node is s Several other resources are the next logical step to explore after running your beacon node: -- If you intend to run a validator, proceed to [become a validator](./mainnet-validator.md); -- Explore how to [manage your keys](./key-management.md); -- Research on [validator management](./validator-management.md); +- If you intend to run a validator, proceed to [become a validator](./mainnet_validator.md); +- Explore how to [manage your keys](./archived_key_management.md); +- Research on [validator management](./validator_management.md); - Dig into the [APIs](./api.md) that the beacon node and validator client provide; -- Study even more about [checkpoint sync](./checkpoint-sync.md); or -- Investigate what steps had to be taken in the past to execute a smooth [merge migration](./merge-migration.md). +- Study even more about [checkpoint sync](./advanced_checkpoint_sync.md); or Finally, if you are struggling with anything, join our [Discord](https://discord.gg/cyAszAh). We are happy to help! diff --git a/book/src/ui-installation.md b/book/src/ui-installation.md deleted file mode 100644 index 9cd84e5160..0000000000 --- a/book/src/ui-installation.md +++ /dev/null @@ -1,73 +0,0 @@ -# 📦 Installation - -Siren supports any operating system that supports containers and/or NodeJS 18, this includes Linux, macOS, and Windows. The recommended way of running Siren is by launching the [docker container](https://hub.docker.com/r/sigp/siren) , but running the application directly is also possible. - -## Version Requirement - -To ensure proper functionality, the Siren app requires Lighthouse v4.3.0 or higher. You can find these versions on the [releases](https://github.com/sigp/lighthouse/releases) page of the Lighthouse repository. - -## Running the Docker container (Recommended) - -The most convenient way to run Siren is to use the Docker images built and published by Sigma Prime. - - They can be found on [Docker hub](https://hub.docker.com/r/sigp/siren/tags), or pulled directly with `docker pull sigp/siren` - -Configuration is done through environment variables, the easiest way to get started is by copying `.env.example` to `.env` and editing the relevant sections (typically, this would at least include adding `BEACON_URL`, `VALIDATOR_URL`, `API_TOKEN` and `SESSION_PASSWORD`) - -Then to run the image: - -`docker compose up` -or -`docker run --rm -ti --name siren -p 4443:443 --env-file $PWD/.env sigp/siren` - -This command will open port 4443, allowing your browser to connect. - -To start Siren, visit `https://localhost:4443` in your web browser. - -Advanced users can mount their own certificates, see the `SSL Certificates` section below - -## Building From Source - -### Docker - -The docker image can be built with the following command: -`docker build -f Dockerfile -t siren .` - -### Building locally - -To build from source, ensure that your system has `Node v18.18` and `yarn` installed. - -#### Build and run the backend - -Navigate to the backend directory `cd backend`. Install all required Node packages by running `yarn`. Once the installation is complete, compile the backend with `yarn build`. Deploy the backend in a production environment, `yarn start:production`. This ensures optimal performance. - -#### Build and run the frontend - -After initializing the backend, return to the root directory. Install all frontend dependencies by executing `yarn`. Build the frontend using `yarn build`. Start the frontend production server with `yarn start`. - -This will allow you to access siren at `http://localhost:3000` by default. - -## Advanced configuration - -### About self-signed SSL certificates - -By default, Siren will generate and use a self-signed certificate on startup. -This will generate a security warning when you try to access the interface. -We recommend to only disable SSL if you would access Siren over a local LAN or otherwise highly trusted or encrypted network (i.e. VPN). - -#### Generating persistent SSL certificates and installing them to your system - -[mkcert](https://github.com/FiloSottile/mkcert) is a tool that makes it super easy to generate a self-signed certificate that is trusted by your browser. - -To use it for `siren`, install it following the instructions. Then, run `mkdir certs; mkcert -cert-file certs/cert.pem -key-file certs/key.pem 127.0.0.1 localhost` (add or replace any IP or hostname that you would use to access it at the end of this command) - -The nginx SSL config inside Siren's container expects 3 files: `/certs/cert.pem` `/certs/key.pem` `/certs/key.pass`. If `/certs/cert.pem` does not exist, it will generate a self-signed certificate as mentioned above. If `/certs/cert.pem` does exist, it will attempt to use your provided or persisted certificates. - -### Configuration through environment variables - -For those who prefer to use environment variables to configure Siren instead of using an `.env` file, this is fully supported. In some cases this may even be preferred. - -#### Docker installed through `snap` - -If you installed Docker through a snap (i.e. on Ubuntu), Docker will have trouble accessing the `.env` file. In this case it is highly recommended to pass the config to the container with environment variables. -Note that the defaults in `.env.example` will be used as fallback, if no other value is provided. diff --git a/book/src/lighthouse-ui.md b/book/src/ui.md similarity index 79% rename from book/src/lighthouse-ui.md rename to book/src/ui.md index f2662f4a69..e980e90268 100644 --- a/book/src/lighthouse-ui.md +++ b/book/src/ui.md @@ -21,11 +21,11 @@ The UI is currently in active development. It resides in the See the following Siren specific topics for more context-specific information: -- [Configuration Guide](./ui-configuration.md) - Explanation of how to setup +- [Configuration Guide](./ui_configuration.md) - Explanation of how to setup and configure Siren. -- [Authentication Guide](./ui-authentication.md) - Explanation of how Siren authentication works and protects validator actions. -- [Usage](./ui-usage.md) - Details various Siren components. -- [FAQs](./ui-faqs.md) - Frequently Asked Questions. +- [Authentication Guide](./ui_authentication.md) - Explanation of how Siren authentication works and protects validator actions. +- [Usage](./ui_usage.md) - Details various Siren components. +- [FAQs](./ui_faqs.md) - Frequently Asked Questions. ## Contributing diff --git a/book/src/ui-authentication.md b/book/src/ui_authentication.md similarity index 87% rename from book/src/ui-authentication.md rename to book/src/ui_authentication.md index 81b867bae2..36e3835e3b 100644 --- a/book/src/ui-authentication.md +++ b/book/src/ui_authentication.md @@ -2,12 +2,12 @@ ## Siren Session -For enhanced security, Siren will require users to authenticate with their session password to access the dashboard. This is crucial because Siren now includes features that can permanently alter the status of the user's validators. The session password must be set during the [configuration](./ui-configuration.md) process before running the Docker or local build, either in an `.env` file or via Docker flags. +For enhanced security, Siren will require users to authenticate with their session password to access the dashboard. This is crucial because Siren now includes features that can permanently alter the status of the user's validators. The session password must be set during the [configuration](./ui_configuration.md) process before running the Docker or local build, either in an `.env` file or via Docker flags. ![exit](imgs/ui-session.png) ## Protected Actions -Prior to executing any sensitive validator action, Siren will request authentication of the session password. If you wish to update your password please refer to the Siren [configuration process](./ui-configuration.md). +Prior to executing any sensitive validator action, Siren will request authentication of the session password. If you wish to update your password please refer to the Siren [configuration process](./ui_configuration.md). ![exit](imgs/ui-auth.png) diff --git a/book/src/ui-configuration.md b/book/src/ui_configuration.md similarity index 99% rename from book/src/ui-configuration.md rename to book/src/ui_configuration.md index 34cc9fe7ca..64b293372b 100644 --- a/book/src/ui-configuration.md +++ b/book/src/ui_configuration.md @@ -29,7 +29,7 @@ We recommend running Siren's container next to your beacon node (on the same ser cd Siren ``` - 1. Create a configuration file in the `Siren` directory: `nano .env` and insert the following fields to the `.env` file. The field values are given here as an example, modify the fields as necessary. For example, the `API_TOKEN` can be obtained from [`Validator Client Authorization Header`](./api-vc-auth-header.md) + 1. Create a configuration file in the `Siren` directory: `nano .env` and insert the following fields to the `.env` file. The field values are given here as an example, modify the fields as necessary. For example, the `API_TOKEN` can be obtained from [`Validator Client Authorization Header`](./api_vc_auth_header.md) A full example with all possible configuration options can be found [here](https://github.com/sigp/siren/blob/stable/.env.example). diff --git a/book/src/ui-faqs.md b/book/src/ui_faqs.md similarity index 92% rename from book/src/ui-faqs.md rename to book/src/ui_faqs.md index 29de889e5f..db365e2fa0 100644 --- a/book/src/ui-faqs.md +++ b/book/src/ui_faqs.md @@ -6,11 +6,11 @@ Yes, the most current Siren version requires Lighthouse v4.3.0 or higher to func ## 2. Where can I find my API token? -The required API token may be found in the default data directory of the validator client. For more information please refer to the lighthouse ui configuration [`api token section`](./api-vc-auth-header.md). +The required API token may be found in the default data directory of the validator client. For more information please refer to the lighthouse ui configuration [`api token section`](./api_vc_auth_header.md). ## 3. How do I fix the Node Network Errors? -If you receive a red notification with a BEACON or VALIDATOR NODE NETWORK ERROR you can refer to the lighthouse ui [`configuration`](./ui-configuration.md#configuration). +If you receive a red notification with a BEACON or VALIDATOR NODE NETWORK ERROR you can refer to the lighthouse ui [`configuration`](./ui_configuration.md#configuration). ## 4. How do I connect Siren to Lighthouse from a different computer on the same network? @@ -19,7 +19,7 @@ That being said, it is entirely possible to have it published over the internet, ## 5. How can I use Siren to monitor my validators remotely when I am not at home? -Most contemporary home routers provide options for VPN access in various ways. A VPN permits a remote computer to establish a connection with internal computers within a home network. With a VPN configuration in place, connecting to the VPN enables you to treat your computer as if it is part of your local home network. The connection process involves following the setup steps for connecting via another machine on the same network on the Siren configuration page and [`configuration`](./ui-configuration.md#configuration). +Most contemporary home routers provide options for VPN access in various ways. A VPN permits a remote computer to establish a connection with internal computers within a home network. With a VPN configuration in place, connecting to the VPN enables you to treat your computer as if it is part of your local home network. The connection process involves following the setup steps for connecting via another machine on the same network on the Siren configuration page and [`configuration`](./ui_configuration.md#configuration). ## 6. Does Siren support reverse proxy or DNS named addresses? diff --git a/book/src/ui-usage.md b/book/src/ui_usage.md similarity index 100% rename from book/src/ui-usage.md rename to book/src/ui_usage.md diff --git a/book/src/validator-doppelganger.md b/book/src/validator_doppelganger.md similarity index 98% rename from book/src/validator-doppelganger.md rename to book/src/validator_doppelganger.md index a3d60d31b3..006df50bd9 100644 --- a/book/src/validator-doppelganger.md +++ b/book/src/validator_doppelganger.md @@ -1,8 +1,8 @@ # Doppelganger Protection [doppelgänger]: https://en.wikipedia.org/wiki/Doppelg%C3%A4nger -[Slashing Protection]: ./slashing-protection.md -[VC HTTP API]: ./api-vc.md +[Slashing Protection]: ./validator_slashing_protection.md +[VC HTTP API]: ./api_vc.md From Lighthouse `v1.5.0`, the *Doppelganger Protection* feature is available for the Validator Client. Taken from the German *[doppelgänger]*, which translates literally to "double-walker", a diff --git a/book/src/suggested-fee-recipient.md b/book/src/validator_fee_recipient.md similarity index 96% rename from book/src/suggested-fee-recipient.md rename to book/src/validator_fee_recipient.md index 4a9be7b963..2b125f5033 100644 --- a/book/src/suggested-fee-recipient.md +++ b/book/src/validator_fee_recipient.md @@ -82,7 +82,7 @@ validator client in order for the execution node to be given adequate notice of ## Setting the fee recipient dynamically using the keymanager API -When the [validator client API](api-vc.md) is enabled, the +When the [validator client API](api_vc.md) is enabled, the [standard keymanager API](https://ethereum.github.io/keymanager-APIs/) includes an endpoint for setting the fee recipient dynamically for a given public key. When used, the fee recipient will be saved in `validator_definitions.yml` so that it persists across restarts of the validator @@ -92,7 +92,7 @@ client. |-------------------|--------------------------------------------| | Path | `/eth/v1/validator/{pubkey}/feerecipient` | | Method | POST | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 202, 404 | ### Example Request Body @@ -117,7 +117,7 @@ curl -X POST \ http://localhost:5062/eth/v1/validator/${PUBKEY}/feerecipient | jq ``` -Note that an authorization header is required to interact with the API. This is specified with the header `-H "Authorization: Bearer $(cat ${DATADIR}/validators/api-token.txt)"` which read the API token to supply the authentication. Refer to [Authorization Header](./api-vc-auth-header.md) for more information. If you are having permission issue with accessing the API token file, you can modify the header to become `-H "Authorization: Bearer $(sudo cat ${DATADIR}/validators/api-token.txt)"`. +Note that an authorization header is required to interact with the API. This is specified with the header `-H "Authorization: Bearer $(cat ${DATADIR}/validators/api-token.txt)"` which read the API token to supply the authentication. Refer to [Authorization Header](./api_vc_auth_header.md) for more information. If you are having permission issue with accessing the API token file, you can modify the header to become `-H "Authorization: Bearer $(sudo cat ${DATADIR}/validators/api-token.txt)"`. #### Successful Response (202) @@ -135,7 +135,7 @@ The same path with a `GET` request can be used to query the fee recipient for a |-------------------|--------------------------------------------| | Path | `/eth/v1/validator/{pubkey}/feerecipient` | | Method | GET | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 200, 404 | Command: @@ -170,7 +170,7 @@ This is useful if you want the fee recipient to fall back to the validator clien |-------------------|--------------------------------------------| | Path | `/eth/v1/validator/{pubkey}/feerecipient` | | Method | DELETE | -| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Required Headers | [`Authorization`](./api_vc_auth_header.md) | | Typical Responses | 204, 404 | Command: diff --git a/book/src/graffiti.md b/book/src/validator_graffiti.md similarity index 95% rename from book/src/graffiti.md rename to book/src/validator_graffiti.md index 7b402ea866..9908d056da 100644 --- a/book/src/graffiti.md +++ b/book/src/validator_graffiti.md @@ -32,7 +32,7 @@ Lighthouse will first search for the graffiti corresponding to the public key of Users can set validator specific graffitis in `validator_definitions.yml` with the `graffiti` key. This option is recommended for static setups where the graffitis won't change on every new block proposal. -You can also update the graffitis in the `validator_definitions.yml` file using the [Lighthouse API](api-vc-endpoints.html#patch-lighthousevalidatorsvoting_pubkey). See example in [Set Graffiti via HTTP](#set-graffiti-via-http). +You can also update the graffitis in the `validator_definitions.yml` file using the [Lighthouse API](api_vc_endpoints.html#patch-lighthousevalidatorsvoting_pubkey). See example in [Set Graffiti via HTTP](#set-graffiti-via-http). Below is an example of the validator_definitions.yml with validator specific graffitis: @@ -74,11 +74,11 @@ Usage: `lighthouse bn --graffiti fortytwo` ## Set Graffiti via HTTP -Use the [Lighthouse API](api-vc-endpoints.md) to set graffiti on a per-validator basis. This method updates the graffiti +Use the [Lighthouse API](api_vc_endpoints.md) to set graffiti on a per-validator basis. This method updates the graffiti both in memory and in the `validator_definitions.yml` file. The new graffiti will be used in the next block proposal without requiring a validator client restart. -Refer to [Lighthouse API](api-vc-endpoints.html#patch-lighthousevalidatorsvoting_pubkey) for API specification. +Refer to [Lighthouse API](api_vc_endpoints.html#patch-lighthousevalidatorsvoting_pubkey) for API specification. ### Example Command diff --git a/book/src/validator-management.md b/book/src/validator_management.md similarity index 98% rename from book/src/validator-management.md rename to book/src/validator_management.md index b9610b6967..18abfb1538 100644 --- a/book/src/validator-management.md +++ b/book/src/validator_management.md @@ -13,7 +13,7 @@ standard directories and do not start their `lighthouse vc` with the this document. However, users with more complex needs may find this document useful. -The [lighthouse validator-manager](./validator-manager.md) command can be used +The [lighthouse validator-manager](./validator_manager.md) command can be used to create and import validators to a Lighthouse VC. It can also be used to move validators between two Lighthouse VCs. @@ -54,7 +54,7 @@ Each permitted field of the file is listed below for reference: - `enabled`: A `true`/`false` indicating if the validator client should consider this validator "enabled". - `voting_public_key`: A validator public key. -- `type`: How the validator signs messages (this can be `local_keystore` or `web3signer` (see [Web3Signer](./validator-web3signer.md))). +- `type`: How the validator signs messages (this can be `local_keystore` or `web3signer` (see [Web3Signer](./advanced_web3signer.md))). - `voting_keystore_path`: The path to a EIP-2335 keystore. - `voting_keystore_password_path`: The path to the password for the EIP-2335 keystore. - `voting_keystore_password`: The password to the EIP-2335 keystore. diff --git a/book/src/validator-manager.md b/book/src/validator_manager.md similarity index 93% rename from book/src/validator-manager.md rename to book/src/validator_manager.md index 11df2af037..c610340b39 100644 --- a/book/src/validator-manager.md +++ b/book/src/validator_manager.md @@ -30,6 +30,6 @@ The `validator-manager` boasts the following features: ## Guides -- [Creating and importing validators using the `create` and `import` commands.](./validator-manager-create.md) -- [Moving validators between two VCs using the `move` command.](./validator-manager-move.md) -- [Managing validators such as delete, import and list validators.](./validator-manager-api.md) +- [Creating and importing validators using the `create` and `import` commands.](./validator_manager_create.md) +- [Moving validators between two VCs using the `move` command.](./validator_manager_move.md) +- [Managing validators such as delete, import and list validators.](./validator_manager_api.md) diff --git a/book/src/validator-manager-api.md b/book/src/validator_manager_api.md similarity index 100% rename from book/src/validator-manager-api.md rename to book/src/validator_manager_api.md diff --git a/book/src/validator-manager-create.md b/book/src/validator_manager_create.md similarity index 98% rename from book/src/validator-manager-create.md rename to book/src/validator_manager_create.md index b4c86dc6da..458907bc65 100644 --- a/book/src/validator-manager-create.md +++ b/book/src/validator_manager_create.md @@ -69,7 +69,7 @@ lighthouse \ > Be sure to remove `./validators.json` after the import is successful since it > contains unencrypted validator keystores. -> Note: To import validators with validator-manager using keystore files created using the staking deposit CLI, refer to [Managing Validators](./validator-manager-api.md#import). +> Note: To import validators with validator-manager using keystore files created using the staking deposit CLI, refer to [Managing Validators](./validator_manager_api.md#import). ## Detailed Guide @@ -179,7 +179,7 @@ INFO Modified key_cache saved successfully The WARN message means that the `validators.json` file does not contain the slashing protection data. This is normal if you are starting a new validator. The flag `--enable-doppelganger-protection` will also protect users from potential slashing risk. The validators will now go through 2-3 epochs of [doppelganger -protection](./validator-doppelganger.md) and will automatically start performing +protection](./validator_doppelganger.md) and will automatically start performing their duties when they are deposited and activated. If the host VC contains the same public key as the `validators.json` file, an error will be shown and the `import` process will stop: diff --git a/book/src/validator-manager-move.md b/book/src/validator_manager_move.md similarity index 100% rename from book/src/validator-manager-move.md rename to book/src/validator_manager_move.md diff --git a/book/src/validator-monitoring.md b/book/src/validator_monitoring.md similarity index 98% rename from book/src/validator-monitoring.md rename to book/src/validator_monitoring.md index bbc95460ec..d7f00521c4 100644 --- a/book/src/validator-monitoring.md +++ b/book/src/validator_monitoring.md @@ -5,7 +5,7 @@ Generally users will want to use this function to track their own validators, ho used for any validator, regardless of who controls it. _Note: If you are looking for remote metric monitoring, please see the docs on -[Prometheus Metrics](./advanced_metrics.md)_. +[Prometheus Metrics](./api_metrics.md)_. ## Monitoring is in the Beacon Node @@ -64,7 +64,7 @@ lighthouse bn --validator-monitor-pubkeys 0x933ad9491b62059dd065b560d256d8957a8c Enrolling a validator for additional monitoring results in: - Additional logs to be printed during BN operation. -- Additional [Prometheus metrics](./advanced_metrics.md) from the BN. +- Additional [Prometheus metrics](./api_metrics.md) from the BN. ### Logging diff --git a/book/src/slashing-protection.md b/book/src/validator_slashing_protection.md similarity index 97% rename from book/src/slashing-protection.md rename to book/src/validator_slashing_protection.md index 2d580f1c31..3e0fe184e5 100644 --- a/book/src/slashing-protection.md +++ b/book/src/validator_slashing_protection.md @@ -22,9 +22,9 @@ and carefully to keep your validators safe. See the [Troubleshooting](#troublesh The database will be automatically created, and your validators registered with it when: * Importing keys from another source (e.g. [staking-deposit-cli](https://github.com/ethereum/staking-deposit-cli/releases), Lodestar, Nimbus, Prysm, Teku, [ethdo](https://github.com/wealdtech/ethdo)). - See [import validator keys](./mainnet-validator.md#step-3-import-validator-keys-to-lighthouse). + See [import validator keys](./mainnet_validator.md#step-3-import-validator-keys-to-lighthouse). * Creating keys using Lighthouse itself (`lighthouse account validator create`) -* Creating keys via the [validator client API](./api-vc.md). +* Creating keys via the [validator client API](./api_vc.md). ## Avoiding Slashing @@ -79,7 +79,7 @@ lighthouse account validator slashing-protection import filename.json ``` When importing an interchange file, you still need to import the validator keystores themselves -separately, using the instructions for [import validator keys](./mainnet-validator.md#step-3-import-validator-keys-to-lighthouse). +separately, using the instructions for [import validator keys](./mainnet_validator.md#step-3-import-validator-keys-to-lighthouse). --- diff --git a/book/src/partial-withdrawal.md b/book/src/validator_sweep.md similarity index 75% rename from book/src/partial-withdrawal.md rename to book/src/validator_sweep.md index 26003e1f2f..b707988e84 100644 --- a/book/src/partial-withdrawal.md +++ b/book/src/validator_sweep.md @@ -1,15 +1,15 @@ -# Partial Withdrawals +# Validator "Sweeping" (Automatic Partial Withdrawals) After the [Capella](https://ethereum.org/en/history/#capella) upgrade on 12th April 2023: - if a validator has a withdrawal credential type `0x00`, the rewards will continue to accumulate and will be locked in the beacon chain. -- if a validator has a withdrawal credential type `0x01`, any rewards above 32ETH will be periodically withdrawn to the withdrawal address. This is also known as the "validator sweep", i.e., once the "validator sweep" reaches your validator's index, your rewards will be withdrawn to the withdrawal address. At the time of writing, with 560,000+ validators on the Ethereum mainnet, you shall expect to receive the rewards approximately every 5 days. +- if a validator has a withdrawal credential type `0x01`, any rewards above 32ETH will be periodically withdrawn to the withdrawal address. This is also known as the "validator sweep", i.e., once the "validator sweep" reaches your validator's index, your rewards will be withdrawn to the withdrawal address. The validator sweep is automatic and it does not incur any fees to withdraw. ## FAQ 1. How to know if I have the withdrawal credentials type `0x00` or `0x01`? - Refer [here](./voluntary-exit.md#1-how-to-know-if-i-have-the-withdrawal-credentials-type-0x01). + Refer [here](./validator_voluntary_exit.md#1-how-to-know-if-i-have-the-withdrawal-credentials-type-0x01). 2. My validator has withdrawal credentials type `0x00`, is there a deadline to update my withdrawal credentials? @@ -17,7 +17,7 @@ After the [Capella](https://ethereum.org/en/history/#capella) upgrade on 12 3. Do I have to do anything to get my rewards after I update the withdrawal credentials to type `0x01`? - No. The "validator sweep" occurs automatically and you can expect to receive the rewards every *n* days, [more information here](./voluntary-exit.md#4-when-will-i-get-my-staked-fund-after-voluntary-exit-if-my-validator-is-of-type-0x01). + No. The "validator sweep" occurs automatically and you can expect to receive the rewards every *n* days, [more information here](./validator_voluntary_exit.md#4-when-will-i-get-my-staked-fund-after-voluntary-exit-if-my-validator-is-of-type-0x01). Figure below summarizes partial withdrawals. diff --git a/book/src/voluntary-exit.md b/book/src/validator_voluntary_exit.md similarity index 100% rename from book/src/voluntary-exit.md rename to book/src/validator_voluntary_exit.md diff --git a/boot_node/Cargo.toml b/boot_node/Cargo.toml index 7c8d2b16fd..5638be0564 100644 --- a/boot_node/Cargo.toml +++ b/boot_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "boot_node" -version = "6.0.1" +version = "7.0.0-beta.5" authors = ["Sigma Prime "] edition = { workspace = true } @@ -16,9 +16,7 @@ lighthouse_network = { workspace = true } log = { workspace = true } logging = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -slog-async = { workspace = true } -slog-scope = "4.3.0" -slog-term = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/boot_node/src/config.rs b/boot_node/src/config.rs index bb7678631f..c43a8b397b 100644 --- a/boot_node/src/config.rs +++ b/boot_node/src/config.rs @@ -53,9 +53,7 @@ impl BootNodeConfig { let mut network_config = NetworkConfig::default(); - let logger = slog_scope::logger(); - - set_network_config(&mut network_config, matches, &data_dir, &logger)?; + set_network_config(&mut network_config, matches, &data_dir)?; // Set the Enr Discovery ports to the listening ports if not present. if let Some(listening_addr_v4) = network_config.listen_addrs().v4() { @@ -85,7 +83,7 @@ impl BootNodeConfig { network_config.discv5_config.enr_update = false; } - let private_key = load_private_key(&network_config, &logger); + let private_key = load_private_key(&network_config); let local_key = CombinedKey::from_libp2p(private_key)?; let local_enr = if let Some(dir) = matches.get_one::("network-dir") { @@ -104,7 +102,7 @@ impl BootNodeConfig { if eth2_network_config.genesis_state_is_known() { let mut genesis_state = eth2_network_config - .genesis_state::(genesis_state_url.as_deref(), genesis_state_url_timeout, &logger).await? + .genesis_state::(genesis_state_url.as_deref(), genesis_state_url_timeout).await? .ok_or_else(|| { "The genesis state for this network is not known, this is an unsupported mode" .to_string() @@ -113,7 +111,7 @@ impl BootNodeConfig { let genesis_state_root = genesis_state .canonical_root() .map_err(|e| format!("Error hashing genesis state: {e:?}"))?; - slog::info!(logger, "Genesis state found"; "root" => ?genesis_state_root); + tracing::info!(root = ?genesis_state_root, "Genesis state found"); let enr_fork = spec.enr_fork_id::( types::Slot::from(0u64), genesis_state.genesis_validators_root(), @@ -121,10 +119,7 @@ impl BootNodeConfig { Some(enr_fork.as_ssz_bytes()) } else { - slog::warn!( - logger, - "No genesis state provided. No Eth2 field added to the ENR" - ); + tracing::warn!("No genesis state provided. No Eth2 field added to the ENR"); None } }; @@ -160,7 +155,7 @@ impl BootNodeConfig { .map_err(|e| format!("Failed to build ENR: {:?}", e))? }; - use_or_load_enr(&local_key, &mut local_enr, &network_config, &logger)?; + use_or_load_enr(&local_key, &mut local_enr, &network_config)?; local_enr }; diff --git a/boot_node/src/lib.rs b/boot_node/src/lib.rs index 669b126bd3..70a45b2f92 100644 --- a/boot_node/src/lib.rs +++ b/boot_node/src/lib.rs @@ -1,6 +1,5 @@ //! Creates a simple DISCV5 server which can be used to bootstrap an Eth2 network. use clap::ArgMatches; -use slog::{o, Drain, Level, Logger}; use eth2_network_config::Eth2NetworkConfig; mod cli; @@ -8,10 +7,9 @@ pub mod config; mod server; pub use cli::cli_app; use config::BootNodeConfig; +use tracing_subscriber::EnvFilter; use types::{EthSpec, EthSpecId}; -const LOG_CHANNEL_SIZE: usize = 2048; - /// Run the bootnode given the CLI configuration. pub fn run( lh_matches: &ArgMatches, @@ -20,49 +18,27 @@ pub fn run( eth2_network_config: &Eth2NetworkConfig, debug_level: String, ) { - let debug_level = match debug_level.as_str() { - "trace" => log::Level::Trace, - "debug" => log::Level::Debug, - "info" => log::Level::Info, - "warn" => log::Level::Warn, - "error" => log::Level::Error, - "crit" => log::Level::Error, - _ => unreachable!(), - }; + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new(debug_level.to_string().to_lowercase())) + .unwrap(); - // Setting up the initial logger format and building it. - let drain = { - let decorator = slog_term::TermDecorator::new().build(); - let decorator = logging::AlignedTermDecorator::new(decorator, logging::MAX_MESSAGE_WIDTH); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - slog_async::Async::new(drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() - }; - - let drain = match debug_level { - log::Level::Info => drain.filter_level(Level::Info), - log::Level::Debug => drain.filter_level(Level::Debug), - log::Level::Trace => drain.filter_level(Level::Trace), - log::Level::Warn => drain.filter_level(Level::Warning), - log::Level::Error => drain.filter_level(Level::Error), - }; - - let log = Logger::root(drain.fuse(), o!()); + tracing_subscriber::fmt() + .with_env_filter(filter_layer) + .init(); // Run the main function emitting any errors if let Err(e) = match eth_spec_id { EthSpecId::Minimal => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } EthSpecId::Mainnet => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } EthSpecId::Gnosis => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } } { - slog::crit!(slog_scope::logger(), "{}", e); + logging::crit!(?e); } } @@ -70,7 +46,6 @@ fn main( lh_matches: &ArgMatches, bn_matches: &ArgMatches, eth2_network_config: &Eth2NetworkConfig, - log: slog::Logger, ) -> Result<(), String> { // Builds a custom executor for the bootnode let runtime = tokio::runtime::Builder::new_multi_thread() @@ -83,7 +58,6 @@ fn main( lh_matches, bn_matches, eth2_network_config, - log, ))?; Ok(()) diff --git a/boot_node/src/server.rs b/boot_node/src/server.rs index 96032dddcc..d96ac0c726 100644 --- a/boot_node/src/server.rs +++ b/boot_node/src/server.rs @@ -8,14 +8,13 @@ use lighthouse_network::{ discv5::{self, enr::NodeId, Discv5}, EnrExt, Eth2Enr, }; -use slog::info; +use tracing::{info, warn}; use types::EthSpec; pub async fn run( lh_matches: &ArgMatches, bn_matches: &ArgMatches, eth2_network_config: &Eth2NetworkConfig, - log: slog::Logger, ) -> Result<(), String> { // parse the CLI args into a useable config let config: BootNodeConfig = BootNodeConfig::new(bn_matches, eth2_network_config).await?; @@ -52,19 +51,19 @@ pub async fn run( let pretty_v4_socket = enr_v4_socket.as_ref().map(|addr| addr.to_string()); let pretty_v6_socket = enr_v6_socket.as_ref().map(|addr| addr.to_string()); info!( - log, "Configuration parameters"; - "listening_address" => ?discv5_config.listen_config, - "advertised_v4_address" => ?pretty_v4_socket, - "advertised_v6_address" => ?pretty_v6_socket, - "eth2" => eth2_field + listening_address = ?discv5_config.listen_config, + advertised_v4_address = ?pretty_v4_socket, + advertised_v6_address = ?pretty_v6_socket, + eth2 = eth2_field, + "Configuration parameters" ); - info!(log, "Identity established"; "peer_id" => %local_enr.peer_id(), "node_id" => %local_enr.node_id()); + info!(peer_id = %local_enr.peer_id(), node_id = %local_enr.node_id(), "Identity established"); // build the contactable multiaddr list, adding the p2p protocol - info!(log, "Contact information"; "enr" => local_enr.to_base64()); - info!(log, "Enr details"; "enr" => ?local_enr); - info!(log, "Contact information"; "multiaddrs" => ?local_enr.multiaddr_p2p()); + info!(enr = local_enr.to_base64(), "Contact information"); + info!(enr = ?local_enr, "Enr details"); + info!(multiaddrs = ?local_enr.multiaddr_p2p(), "Contact information"); // construct the discv5 server let mut discv5: Discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap(); @@ -72,16 +71,15 @@ pub async fn run( // If there are any bootnodes add them to the routing table for enr in boot_nodes { info!( - log, - "Adding bootnode"; - "ipv4_address" => ?enr.udp4_socket(), - "ipv6_address" => ?enr.udp6_socket(), - "peer_id" => ?enr.peer_id(), - "node_id" => ?enr.node_id() + ipv4_address = ?enr.udp4_socket(), + ipv6_address = ?enr.udp6_socket(), + peer_id = ?enr.peer_id(), + node_id = ?enr.node_id(), + "Adding bootnode" ); if enr != local_enr { if let Err(e) = discv5.add_enr(enr) { - slog::warn!(log, "Failed adding ENR"; "error" => ?e); + warn!(error = ?e, "Failed adding ENR"); } } } @@ -93,7 +91,7 @@ pub async fn run( // if there are peers in the local routing table, establish a session by running a query if !discv5.table_entries_id().is_empty() { - info!(log, "Executing bootstrap query..."); + info!("Executing bootstrap query..."); let _ = discv5.find_node(NodeId::random()).await; } @@ -131,14 +129,14 @@ pub async fn run( // display server metrics let metrics = discv5.metrics(); info!( - log, "Server metrics"; - "connected_peers" => discv5.connected_peers(), - "active_sessions" => metrics.active_sessions, - "requests/s" => format_args!("{:.2}", metrics.unsolicited_requests_per_second), - "ipv4_nodes" => ipv4_only_reachable, - "ipv6_only_nodes" => ipv6_only_reachable, - "dual_stack_nodes" => ipv4_ipv6_reachable, - "unreachable_nodes" => unreachable_nodes, + connected_peers = discv5.connected_peers(), + active_sessions = metrics.active_sessions, + "requests/s" = format_args!("{:.2}", metrics.unsolicited_requests_per_second), + ipv4_nodes = ipv4_only_reachable, + ipv6_only_nodes = ipv6_only_reachable, + dual_stack_nodes = ipv4_ipv6_reachable, + unreachable_nodes, + "Server metrics", ); } @@ -149,7 +147,7 @@ pub async fn run( // Ignore these events here } discv5::Event::SocketUpdated(socket_addr) => { - info!(log, "Advertised socket address updated"; "socket_addr" => %socket_addr); + info!(%socket_addr, "Advertised socket address updated"); } _ => {} // Ignore } diff --git a/common/account_utils/Cargo.toml b/common/account_utils/Cargo.toml index 3ab6034688..00c74a1303 100644 --- a/common/account_utils/Cargo.toml +++ b/common/account_utils/Cargo.toml @@ -14,7 +14,7 @@ regex = { workspace = true } rpassword = "5.0.0" serde = { workspace = true } serde_yaml = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_dir = { workspace = true } zeroize = { workspace = true } diff --git a/common/account_utils/src/validator_definitions.rs b/common/account_utils/src/validator_definitions.rs index 7337d6dfb4..4c253283fe 100644 --- a/common/account_utils/src/validator_definitions.rs +++ b/common/account_utils/src/validator_definitions.rs @@ -7,11 +7,11 @@ use crate::{default_keystore_password_path, read_password_string, write_file_via use eth2_keystore::Keystore; use regex::Regex; use serde::{Deserialize, Serialize}; -use slog::{error, Logger}; use std::collections::HashSet; use std::fs::{self, create_dir_all, File}; use std::io; use std::path::{Path, PathBuf}; +use tracing::error; use types::{graffiti::GraffitiString, Address, PublicKey}; use validator_dir::VOTING_KEYSTORE_FILE; use zeroize::Zeroizing; @@ -115,7 +115,6 @@ impl SigningDefinition { voting_keystore_password_path: Some(path), .. } => read_password_string(path) - .map(Into::into) .map(Option::Some) .map_err(Error::UnableToReadKeystorePassword), SigningDefinition::LocalKeystore { .. } => Err(Error::KeystoreWithoutPassword), @@ -267,7 +266,6 @@ impl ValidatorDefinitions { &mut self, validators_dir: P, secrets_dir: P, - log: &Logger, ) -> Result { let mut keystore_paths = vec![]; recursively_find_voting_keystores(validators_dir, &mut keystore_paths) @@ -312,10 +310,9 @@ impl ValidatorDefinitions { Ok(keystore) => keystore, Err(e) => { error!( - log, - "Unable to read validator keystore"; - "error" => e, - "keystore" => format!("{:?}", voting_keystore_path) + error = ?e, + keystore = ?voting_keystore_path, + "Unable to read validator keystore" ); return None; } @@ -337,9 +334,8 @@ impl ValidatorDefinitions { } None => { error!( - log, - "Invalid keystore public key"; - "keystore" => format!("{:?}", voting_keystore_path) + keystore = ?voting_keystore_path, + "Invalid keystore public key" ); return None; } diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index a1bc9d025b..35dd806fb3 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -3,19 +3,20 @@ name = "eth2" version = "0.1.0" authors = ["Paul Hauner "] edition = { workspace = true } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] derivative = { workspace = true } either = { workspace = true } +enr = { version = "0.13.0", features = ["ed25519"] } eth2_keystore = { workspace = true } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } futures = { workspace = true } futures-util = "0.3.8" -lighthouse_network = { workspace = true } +libp2p-identity = { version = "0.2", features = ["peerid"] } mediatype = "0.19.13" +multiaddr = "0.18.2" pretty_reqwest_error = { workspace = true } proto_array = { workspace = true } reqwest = { workspace = true } @@ -25,7 +26,6 @@ serde = { workspace = true } serde_json = { workspace = true } slashing_protection = { workspace = true } ssz_types = { workspace = true } -store = { workspace = true } types = { workspace = true } zeroize = { workspace = true } diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 64cf9f7ce6..57fbb1a4c3 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -16,11 +16,12 @@ pub mod types; use self::mixin::{RequestAccept, ResponseOptional}; use self::types::{Error as ResponseError, *}; +use ::types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse; use derivative::Derivative; use either::Either; use futures::Stream; use futures_util::StreamExt; -use lighthouse_network::PeerId; +use libp2p_identity::PeerId; use pretty_reqwest_error::PrettyReqwestError; pub use reqwest; use reqwest::{ @@ -36,7 +37,6 @@ use std::fmt; use std::future::Future; use std::path::PathBuf; use std::time::Duration; -use store::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse; pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); @@ -333,7 +333,6 @@ impl BeaconNodeHttpClient { } /// Perform a HTTP POST request, returning a JSON response. - #[cfg(feature = "lighthouse")] async fn post_with_response( &self, url: U, @@ -786,6 +785,45 @@ impl BeaconNodeHttpClient { self.get_opt(path).await } + /// `GET beacon/states/{state_id}/pending_deposits` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_states_pending_deposits( + &self, + state_id: StateId, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("states") + .push(&state_id.to_string()) + .push("pending_deposits"); + + self.get_opt(path).await + } + + /// `GET beacon/states/{state_id}/pending_partial_withdrawals` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_states_pending_partial_withdrawals( + &self, + state_id: StateId, + ) -> Result>>, Error> + { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("states") + .push(&state_id.to_string()) + .push("pending_partial_withdrawals"); + + self.get_opt(path).await + } + /// `GET beacon/light_client/updates` /// /// Returns `Ok(None)` on a 404 error. @@ -1606,33 +1644,34 @@ impl BeaconNodeHttpClient { /// `POST beacon/rewards/sync_committee` pub async fn post_beacon_rewards_sync_committee( &self, - rewards: &[Option>], - ) -> Result<(), Error> { + block_id: BlockId, + validators: &[ValidatorId], + ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") - .push("sync_committee"); + .push("sync_committee") + .push(&block_id.to_string()); - self.post(path, &rewards).await?; - - Ok(()) + self.post_with_response(path, &validators).await } /// `GET beacon/rewards/blocks` - pub async fn get_beacon_rewards_blocks(&self, epoch: Epoch) -> Result<(), Error> { + pub async fn get_beacon_rewards_blocks( + &self, + block_id: BlockId, + ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") - .push("blocks"); - - path.query_pairs_mut() - .append_pair("epoch", &epoch.to_string()); + .push("blocks") + .push(&block_id.to_string()); self.get(path).await } @@ -1640,19 +1679,19 @@ impl BeaconNodeHttpClient { /// `POST beacon/rewards/attestations` pub async fn post_beacon_rewards_attestations( &self, - attestations: &[ValidatorId], - ) -> Result<(), Error> { + epoch: Epoch, + validators: &[ValidatorId], + ) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") - .push("attestations"); + .push("attestations") + .push(&epoch.to_string()); - self.post(path, &attestations).await?; - - Ok(()) + self.post_with_response(path, &validators).await } // GET builder/states/{state_id}/expected_withdrawals diff --git a/common/eth2/src/lighthouse.rs b/common/eth2/src/lighthouse.rs index badc4857c4..9a5d9100cf 100644 --- a/common/eth2/src/lighthouse.rs +++ b/common/eth2/src/lighthouse.rs @@ -1,15 +1,15 @@ //! This module contains endpoints that are non-standard and only available on Lighthouse servers. mod attestation_performance; -pub mod attestation_rewards; mod block_packing_efficiency; mod block_rewards; -mod standard_block_rewards; -mod sync_committee_rewards; +pub mod sync_state; use crate::{ + lighthouse::sync_state::SyncState, types::{ - DepositTreeSnapshot, Epoch, EthSpec, FinalizedExecutionBlock, GenericResponse, ValidatorId, + AdminPeer, DepositTreeSnapshot, Epoch, FinalizedExecutionBlock, GenericResponse, + ValidatorId, }, BeaconNodeHttpClient, DepositData, Error, Eth1Data, Hash256, Slot, }; @@ -17,36 +17,20 @@ use proto_array::core::ProtoArray; use serde::{Deserialize, Serialize}; use ssz::four_byte_option_impl; use ssz_derive::{Decode, Encode}; -use store::{AnchorInfo, BlobInfo, Split, StoreConfig}; pub use attestation_performance::{ AttestationPerformance, AttestationPerformanceQuery, AttestationPerformanceStatistics, }; -pub use attestation_rewards::StandardAttestationRewards; pub use block_packing_efficiency::{ BlockPackingEfficiency, BlockPackingEfficiencyQuery, ProposerInfo, UniqueAttestation, }; pub use block_rewards::{AttestationRewards, BlockReward, BlockRewardMeta, BlockRewardsQuery}; -pub use lighthouse_network::{types::SyncState, PeerInfo}; -pub use standard_block_rewards::StandardBlockReward; -pub use sync_committee_rewards::SyncCommitteeReward; // Define "legacy" implementations of `Option` which use four bytes for encoding the union // selector. four_byte_option_impl!(four_byte_option_u64, u64); four_byte_option_impl!(four_byte_option_hash256, Hash256); -/// Information returned by `peers` and `connected_peers`. -// TODO: this should be deserializable.. -#[derive(Debug, Clone, Serialize)] -#[serde(bound = "E: EthSpec")] -pub struct Peer { - /// The Peer's ID - pub peer_id: String, - /// The PeerInfo associated with the peer. - pub peer_info: PeerInfo, -} - /// The results of validators voting during an epoch. /// /// Provides information about the current and previous epochs. @@ -234,15 +218,6 @@ impl From for FinalizedExecutionBlock { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct DatabaseInfo { - pub schema_version: u64, - pub config: StoreConfig, - pub split: Split, - pub anchor: AnchorInfo, - pub blob_info: BlobInfo, -} - impl BeaconNodeHttpClient { /// `GET lighthouse/health` pub async fn get_lighthouse_health(&self) -> Result, Error> { @@ -380,19 +355,6 @@ impl BeaconNodeHttpClient { self.get_opt::<(), _>(path).await.map(|opt| opt.is_some()) } - /// `GET lighthouse/database/info` - pub async fn get_lighthouse_database_info(&self) -> Result { - let mut path = self.server.full.clone(); - - path.path_segments_mut() - .map_err(|()| Error::InvalidUrl(self.server.clone()))? - .push("lighthouse") - .push("database") - .push("info"); - - self.get(path).await - } - /// `POST lighthouse/database/reconstruct` pub async fn post_lighthouse_database_reconstruct(&self) -> Result { let mut path = self.server.full.clone(); @@ -406,6 +368,30 @@ impl BeaconNodeHttpClient { self.post_with_response(path, &()).await } + /// `POST lighthouse/add_peer` + pub async fn post_lighthouse_add_peer(&self, req: AdminPeer) -> Result<(), Error> { + let mut path = self.server.full.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("lighthouse") + .push("add_peer"); + + self.post_with_response(path, &req).await + } + + /// `POST lighthouse/remove_peer` + pub async fn post_lighthouse_remove_peer(&self, req: AdminPeer) -> Result<(), Error> { + let mut path = self.server.full.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("lighthouse") + .push("remove_peer"); + + self.post_with_response(path, &req).await + } + /* Analysis endpoints. */ diff --git a/common/eth2/src/lighthouse/attestation_rewards.rs b/common/eth2/src/lighthouse/attestation_rewards.rs deleted file mode 100644 index fa3f93d06f..0000000000 --- a/common/eth2/src/lighthouse/attestation_rewards.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_utils::quoted_u64::Quoted; - -// Details about the rewards paid for attestations -// All rewards in GWei - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct IdealAttestationRewards { - // Validator's effective balance in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub effective_balance: u64, - // Ideal attester's reward for head vote in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub head: u64, - // Ideal attester's reward for target vote in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub target: u64, - // Ideal attester's reward for source vote in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub source: u64, - // Ideal attester's inclusion_delay reward in gwei (phase0 only) - #[serde(skip_serializing_if = "Option::is_none")] - pub inclusion_delay: Option>, - // Ideal attester's inactivity penalty in gwei - #[serde(with = "serde_utils::quoted_i64")] - pub inactivity: i64, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct TotalAttestationRewards { - // one entry for every validator based on their attestations in the epoch - #[serde(with = "serde_utils::quoted_u64")] - pub validator_index: u64, - // attester's reward for head vote in gwei - #[serde(with = "serde_utils::quoted_i64")] - pub head: i64, - // attester's reward for target vote in gwei - #[serde(with = "serde_utils::quoted_i64")] - pub target: i64, - // attester's reward for source vote in gwei - #[serde(with = "serde_utils::quoted_i64")] - pub source: i64, - // attester's inclusion_delay reward in gwei (phase0 only) - #[serde(skip_serializing_if = "Option::is_none")] - pub inclusion_delay: Option>, - // attester's inactivity penalty in gwei - #[serde(with = "serde_utils::quoted_i64")] - pub inactivity: i64, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct StandardAttestationRewards { - pub ideal_rewards: Vec, - pub total_rewards: Vec, -} diff --git a/common/eth2/src/lighthouse/standard_block_rewards.rs b/common/eth2/src/lighthouse/standard_block_rewards.rs deleted file mode 100644 index 15fcdc6066..0000000000 --- a/common/eth2/src/lighthouse/standard_block_rewards.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -// Details about the rewards for a single block -// All rewards in GWei -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct StandardBlockReward { - // proposer of the block, the proposer index who receives these rewards - #[serde(with = "serde_utils::quoted_u64")] - pub proposer_index: u64, - // total block reward in gwei, - // equal to attestations + sync_aggregate + proposer_slashings + attester_slashings - #[serde(with = "serde_utils::quoted_u64")] - pub total: u64, - // block reward component due to included attestations in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub attestations: u64, - // block reward component due to included sync_aggregate in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub sync_aggregate: u64, - // block reward component due to included proposer_slashings in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub proposer_slashings: u64, - // block reward component due to included attester_slashings in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub attester_slashings: u64, -} diff --git a/common/eth2/src/lighthouse/sync_committee_rewards.rs b/common/eth2/src/lighthouse/sync_committee_rewards.rs deleted file mode 100644 index 66a721dc22..0000000000 --- a/common/eth2/src/lighthouse/sync_committee_rewards.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::{Deserialize, Serialize}; - -// Details about the rewards paid to sync committee members for attesting headers -// All rewards in GWei - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct SyncCommitteeReward { - #[serde(with = "serde_utils::quoted_u64")] - pub validator_index: u64, - // sync committee reward in gwei for the validator - #[serde(with = "serde_utils::quoted_i64")] - pub reward: i64, -} diff --git a/beacon_node/lighthouse_network/src/types/sync_state.rs b/common/eth2/src/lighthouse/sync_state.rs similarity index 100% rename from beacon_node/lighthouse_network/src/types/sync_state.rs rename to common/eth2/src/lighthouse/sync_state.rs diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 35b0de02f7..87ee87f183 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -5,11 +5,13 @@ use crate::{ Error as ServerError, CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER, }; -use lighthouse_network::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus}; +use enr::{CombinedKey, Enr}; use mediatype::{names, MediaType, MediaTypeList}; +use multiaddr::Multiaddr; use reqwest::header::HeaderMap; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; +use serde_utils::quoted_u64::Quoted; use ssz::{Decode, DecodeError}; use ssz_derive::{Decode, Encode}; use std::fmt::{self, Display}; @@ -578,7 +580,7 @@ pub struct ChainHeadData { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IdentityData { pub peer_id: String, - pub enr: Enr, + pub enr: Enr, pub p2p_addresses: Vec, pub discovery_addresses: Vec, pub metadata: MetaData, @@ -861,19 +863,6 @@ pub enum PeerState { Disconnecting, } -impl PeerState { - pub fn from_peer_connection_status(status: &PeerConnectionStatus) -> Self { - match status { - PeerConnectionStatus::Connected { .. } => PeerState::Connected, - PeerConnectionStatus::Dialing { .. } => PeerState::Connecting, - PeerConnectionStatus::Disconnecting { .. } => PeerState::Disconnecting, - PeerConnectionStatus::Disconnected { .. } - | PeerConnectionStatus::Banned { .. } - | PeerConnectionStatus::Unknown => PeerState::Disconnected, - } - } -} - impl FromStr for PeerState { type Err = String; @@ -906,15 +895,6 @@ pub enum PeerDirection { Outbound, } -impl PeerDirection { - pub fn from_connection_direction(direction: &ConnectionDirection) -> Self { - match direction { - ConnectionDirection::Incoming => PeerDirection::Inbound, - ConnectionDirection::Outgoing => PeerDirection::Outbound, - } - } -} - impl FromStr for PeerDirection { type Err = String; @@ -1442,6 +1422,18 @@ pub struct StandardLivenessResponseData { pub is_live: bool, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ManualFinalizationRequestData { + pub state_root: Hash256, + pub epoch: Epoch, + pub block_root: Hash256, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AdminPeer { + pub enr: String, +} + #[derive(Debug, Serialize, Deserialize)] pub struct LivenessRequestData { pub epoch: Epoch, @@ -2084,6 +2076,90 @@ pub struct BlobsBundle { pub blobs: BlobsList, } +/// Details about the rewards paid to sync committee members for attesting headers +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct SyncCommitteeReward { + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + /// sync committee reward in gwei for the validator + #[serde(with = "serde_utils::quoted_i64")] + pub reward: i64, +} + +/// Details about the rewards for a single block +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct StandardBlockReward { + /// proposer of the block, the proposer index who receives these rewards + #[serde(with = "serde_utils::quoted_u64")] + pub proposer_index: u64, + /// total block reward in gwei, + /// equal to attestations + sync_aggregate + proposer_slashings + attester_slashings + #[serde(with = "serde_utils::quoted_u64")] + pub total: u64, + /// block reward component due to included attestations in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub attestations: u64, + /// block reward component due to included sync_aggregate in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub sync_aggregate: u64, + /// block reward component due to included proposer_slashings in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub proposer_slashings: u64, + /// block reward component due to included attester_slashings in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub attester_slashings: u64, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct IdealAttestationRewards { + /// Validator's effective balance in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub effective_balance: u64, + /// Ideal attester's reward for head vote in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub head: u64, + /// Ideal attester's reward for target vote in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub target: u64, + /// Ideal attester's reward for source vote in gwei + #[serde(with = "serde_utils::quoted_u64")] + pub source: u64, + /// Ideal attester's inclusion_delay reward in gwei (phase0 only) + #[serde(skip_serializing_if = "Option::is_none")] + pub inclusion_delay: Option>, + /// Ideal attester's inactivity penalty in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub inactivity: i64, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct TotalAttestationRewards { + /// one entry for every validator based on their attestations in the epoch + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + /// attester's reward for head vote in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub head: i64, + /// attester's reward for target vote in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub target: i64, + /// attester's reward for source vote in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub source: i64, + /// attester's inclusion_delay reward in gwei (phase0 only) + #[serde(skip_serializing_if = "Option::is_none")] + pub inclusion_delay: Option>, + /// attester's inactivity penalty in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub inactivity: i64, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct StandardAttestationRewards { + pub ideal_rewards: Vec, + pub total_rewards: Vec, +} + #[cfg(test)] mod test { use super::*; diff --git a/common/eth2_config/src/lib.rs b/common/eth2_config/src/lib.rs index 50386feb8a..017bdf288d 100644 --- a/common/eth2_config/src/lib.rs +++ b/common/eth2_config/src/lib.rs @@ -35,6 +35,17 @@ const HOLESKY_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url genesis_state_root: "0x0ea3f6f9515823b59c863454675fefcd1d8b4f2dbe454db166206a41fda060a0", }; +const HOODI_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url { + urls: &[ + // This is an AWS S3 bucket hosted by Sigma Prime. See Paul Hauner for + // more details. + "https://sigp-public-genesis-states.s3.ap-southeast-2.amazonaws.com/hoodi/", + ], + checksum: "0x7f42257ef69e055496c964a753bb07e54001ccd57ab467ef72d67af086bcfce7", + genesis_validators_root: "0x212f13fc4df078b6cb7db228f1c8307566dcecf900867401a92023d7ba99cb5f", + genesis_state_root: "0x2683ebc120f91f740c7bed4c866672d01e1ba51b4cc360297138465ee5df40f0", +}; + const CHIADO_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url { // No default checkpoint sources are provided. urls: &[], @@ -328,5 +339,14 @@ define_hardcoded_nets!( "holesky", // Describes how the genesis state can be obtained. HOLESKY_GENESIS_STATE_SOURCE + ), + ( + // Network name (must be unique among all networks). + hoodi, + // The name of the directory in the `eth2_network_config/built_in_network_configs` + // directory where the configuration files are located for this network. + "hoodi", + // Describes how the genesis state can be obtained. + HOODI_GENESIS_STATE_SOURCE ) ); diff --git a/common/eth2_network_config/Cargo.toml b/common/eth2_network_config/Cargo.toml index a255e04229..da6c4dfd95 100644 --- a/common/eth2_network_config/Cargo.toml +++ b/common/eth2_network_config/Cargo.toml @@ -20,12 +20,11 @@ bytes = { workspace = true } discv5 = { workspace = true } eth2_config = { workspace = true } kzg = { workspace = true } -logging = { workspace = true } pretty_reqwest_error = { workspace = true } reqwest = { workspace = true } sensitive_url = { workspace = true } serde_yaml = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } url = { workspace = true } diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index 35ba3af28b..1455ec5f63 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -46,7 +46,7 @@ DENEB_FORK_VERSION: 0x0400006f DENEB_FORK_EPOCH: 516608 # Wed Jan 31 2024 18:15:40 GMT+0000 # Electra ELECTRA_FORK_VERSION: 0x0500006f -ELECTRA_FORK_EPOCH: 18446744073709551615 +ELECTRA_FORK_EPOCH: 948224 # Thu Mar 6 2025 09:43:40 GMT+0000 # Fulu FULU_FORK_VERSION: 0x0600006f FULU_FORK_EPOCH: 18446744073709551615 @@ -138,6 +138,18 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # `uint64(6)` MAX_BLOBS_PER_BLOCK: 6 +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**6 * 10**9 (= 64,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000 +# `2` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2 +# `uint64(2)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 2 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256 + # DAS NUMBER_OF_COLUMNS: 128 NUMBER_OF_CUSTODY_GROUPS: 128 diff --git a/common/eth2_network_config/built_in_network_configs/hoodi/boot_enr.yaml b/common/eth2_network_config/built_in_network_configs/hoodi/boot_enr.yaml new file mode 100644 index 0000000000..33eaa7e8a9 --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/hoodi/boot_enr.yaml @@ -0,0 +1,13 @@ +# hoodi consensus layer bootnodes +# --------------------------------------- +# 1. Tag nodes with maintainer +# 2. Keep nodes updated +# 3. Review PRs: check ENR duplicates, fork-digest, connection. + +# EF +- enr:-Mq4QLkmuSwbGBUph1r7iHopzRpdqE-gcm5LNZfcE-6T37OCZbRHi22bXZkaqnZ6XdIyEDTelnkmMEQB8w6NbnJUt9GGAZWaowaYh2F0dG5ldHOIABgAAAAAAACEZXRoMpDS8Zl_YAAJEAAIAAAAAAAAgmlkgnY0gmlwhNEmfKCEcXVpY4IyyIlzZWNwMjU2azGhA0hGa4jZJZYQAS-z6ZFK-m4GCFnWS8wfjO0bpSQn6hyEiHN5bmNuZXRzAIN0Y3CCIyiDdWRwgiMo +- enr:-Ku4QLVumWTwyOUVS4ajqq8ZuZz2ik6t3Gtq0Ozxqecj0qNZWpMnudcvTs-4jrlwYRQMQwBS8Pvtmu4ZPP2Lx3i2t7YBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpBd9cEGEAAJEP__________gmlkgnY0gmlwhNEmfKCJc2VjcDI1NmsxoQLdRlI8aCa_ELwTJhVN8k7km7IDc3pYu-FMYBs5_FiigIN1ZHCCIyk +- enr:-LK4QAYuLujoiaqCAs0-qNWj9oFws1B4iy-Hff1bRB7wpQCYSS-IIMxLWCn7sWloTJzC1SiH8Y7lMQ5I36ynGV1ASj4Eh2F0dG5ldHOIYAAAAAAAAACEZXRoMpDS8Zl_YAAJEAAIAAAAAAAAgmlkgnY0gmlwhIbRilSJc2VjcDI1NmsxoQOmI5MlAu3f5WEThAYOqoygpS2wYn0XS5NV2aYq7T0a04N0Y3CCIyiDdWRwgiMo +- enr:-Ku4QIC89sMC0o-irosD4_23lJJ4qCGOvdUz7SmoShWx0k6AaxCFTKviEHa-sa7-EzsiXpDp0qP0xzX6nKdXJX3X-IQBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpBd9cEGEAAJEP__________gmlkgnY0gmlwhIbRilSJc2VjcDI1NmsxoQK_m0f1DzDc9Cjrspm36zuRa7072HSiMGYWLsKiVSbP34N1ZHCCIyk +- enr:-Ku4QNkWjw5tNzo8DtWqKm7CnDdIq_y7xppD6c1EZSwjB8rMOkSFA1wJPLoKrq5UvA7wcxIotH6Usx3PAugEN2JMncIBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpBd9cEGEAAJEP__________gmlkgnY0gmlwhIbHuBeJc2VjcDI1NmsxoQP3FwrhFYB60djwRjAoOjttq6du94DtkQuaN99wvgqaIYN1ZHCCIyk +- enr:-OS4QMJGE13xEROqvKN1xnnt7U-noc51VXyM6wFMuL9LMhQDfo1p1dF_zFdS4OsnXz_vIYk-nQWnqJMWRDKvkSK6_CwDh2F0dG5ldHOIAAAAADAAAACGY2xpZW502IpMaWdodGhvdXNljDcuMC4wLWJldGEuM4RldGgykNLxmX9gAAkQAAgAAAAAAACCaWSCdjSCaXCEhse4F4RxdWljgiMqiXNlY3AyNTZrMaECef77P8k5l3PC_raLw42OAzdXfxeQ-58BJriNaqiRGJSIc3luY25ldHMAg3RjcIIjKIN1ZHCCIyg diff --git a/common/eth2_network_config/built_in_network_configs/hoodi/config.yaml b/common/eth2_network_config/built_in_network_configs/hoodi/config.yaml new file mode 100644 index 0000000000..19d7797424 --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/hoodi/config.yaml @@ -0,0 +1,165 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: hoodi + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 +# 2025-Mar-17 12:00:00 PM UTC +MIN_GENESIS_TIME: 1742212800 +GENESIS_FORK_VERSION: 0x10000910 +GENESIS_DELAY: 600 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20000910 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30000910 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40000910 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50000910 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60000910 +ELECTRA_FORK_EPOCH: 2048 + +# Fulu +FULU_FORK_VERSION: 0x70000910 +FULU_FORK_EPOCH: 18446744073709551615 + + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 65536 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 560048 +DEPOSIT_NETWORK_ID: 560048 +DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(6)` +TARGET_BLOBS_PER_BLOCK_ELECTRA: 6 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# Fulu +NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 +MAX_BLOBS_PER_BLOCK_FULU: 12 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 diff --git a/common/eth2_network_config/built_in_network_configs/hoodi/deposit_contract_block.txt b/common/eth2_network_config/built_in_network_configs/hoodi/deposit_contract_block.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/hoodi/deposit_contract_block.txt @@ -0,0 +1 @@ +0 diff --git a/common/eth2_network_config/src/lib.rs b/common/eth2_network_config/src/lib.rs index 5d5a50574b..0bb12c4187 100644 --- a/common/eth2_network_config/src/lib.rs +++ b/common/eth2_network_config/src/lib.rs @@ -19,12 +19,12 @@ use pretty_reqwest_error::PrettyReqwestError; use reqwest::{Client, Error}; use sensitive_url::SensitiveUrl; use sha2::{Digest, Sha256}; -use slog::{info, warn, Logger}; use std::fs::{create_dir_all, File}; use std::io::{Read, Write}; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use tracing::{info, warn}; use types::{BeaconState, ChainSpec, Config, EthSpec, EthSpecId, Hash256}; use url::Url; @@ -198,7 +198,6 @@ impl Eth2NetworkConfig { &self, genesis_state_url: Option<&str>, timeout: Duration, - log: &Logger, ) -> Result>, String> { let spec = self.chain_spec::()?; match &self.genesis_state_source { @@ -217,9 +216,9 @@ impl Eth2NetworkConfig { format!("Unable to parse genesis state bytes checksum: {:?}", e) })?; let bytes = if let Some(specified_url) = genesis_state_url { - download_genesis_state(&[specified_url], timeout, checksum, log).await + download_genesis_state(&[specified_url], timeout, checksum).await } else { - download_genesis_state(built_in_urls, timeout, checksum, log).await + download_genesis_state(built_in_urls, timeout, checksum).await }?; let state = BeaconState::from_ssz_bytes(bytes.as_ref(), &spec).map_err(|e| { format!("Downloaded genesis state SSZ bytes are invalid: {:?}", e) @@ -387,7 +386,6 @@ async fn download_genesis_state( urls: &[&str], timeout: Duration, checksum: Hash256, - log: &Logger, ) -> Result, String> { if urls.is_empty() { return Err( @@ -407,11 +405,10 @@ async fn download_genesis_state( .unwrap_or_else(|_| "".to_string()); info!( - log, - "Downloading genesis state"; - "server" => &redacted_url, - "timeout" => ?timeout, - "info" => "this may take some time on testnets with large validator counts" + server = &redacted_url, + timeout = ?timeout, + info = "this may take some time on testnets with large validator counts", + "Downloading genesis state" ); let client = Client::new(); @@ -424,10 +421,9 @@ async fn download_genesis_state( return Ok(bytes.into()); } else { warn!( - log, - "Genesis state download failed"; - "server" => &redacted_url, - "timeout" => ?timeout, + server = &redacted_url, + timeout = ?timeout, + "Genesis state download failed" ); errors.push(format!( "Response from {} did not match local checksum", @@ -505,7 +501,7 @@ mod tests { async fn mainnet_genesis_state() { let config = Eth2NetworkConfig::from_hardcoded_net(&MAINNET).unwrap(); config - .genesis_state::(None, Duration::from_secs(1), &logging::test_logger()) + .genesis_state::(None, Duration::from_secs(1)) .await .expect("beacon state can decode"); } diff --git a/common/lighthouse_version/Cargo.toml b/common/lighthouse_version/Cargo.toml index 164e3e47a7..cb4a43e407 100644 --- a/common/lighthouse_version/Cargo.toml +++ b/common/lighthouse_version/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lighthouse_version" version = "0.1.0" -authors = ["Paul Hauner "] +authors = ["Sigma Prime "] edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/lighthouse_version/src/lib.rs b/common/lighthouse_version/src/lib.rs index a35b8c42c1..bd5e31e3ab 100644 --- a/common/lighthouse_version/src/lib.rs +++ b/common/lighthouse_version/src/lib.rs @@ -17,8 +17,8 @@ pub const VERSION: &str = git_version!( // NOTE: using --match instead of --exclude for compatibility with old Git "--match=thiswillnevermatchlol" ], - prefix = "Lighthouse/v6.0.1-", - fallback = "Lighthouse/v6.0.1" + prefix = "Lighthouse/v7.0.0-beta.5-", + fallback = "Lighthouse/v7.0.0-beta.5" ); /// Returns the first eight characters of the latest commit hash for this build. @@ -54,7 +54,7 @@ pub fn version_with_platform() -> String { /// /// `1.5.1` pub fn version() -> &'static str { - "6.0.1" + "7.0.0-beta.5" } /// Returns the name of the current client running. @@ -71,9 +71,10 @@ mod test { #[test] fn version_formatting() { - let re = - Regex::new(r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-rc.[0-9])?(-[[:xdigit:]]{7})?\+?$") - .unwrap(); + let re = Regex::new( + r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta).[0-9])?(-[[:xdigit:]]{7})?\+?$", + ) + .unwrap(); assert!( re.is_match(VERSION), "version doesn't match regex: {}", diff --git a/common/logging/Cargo.toml b/common/logging/Cargo.toml index b2829a48d8..a69bc6ab23 100644 --- a/common/logging/Cargo.toml +++ b/common/logging/Cargo.toml @@ -9,14 +9,12 @@ test_logger = [] # Print log output to stderr when running tests instead of drop [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +logroller = { workspace = true } metrics = { workspace = true } +once_cell = "1.17.1" parking_lot = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } -slog-term = { workspace = true } -sloggers = { workspace = true } -take_mut = "0.2.2" tokio = { workspace = true, features = [ "time" ] } tracing = "0.1" tracing-appender = { workspace = true } diff --git a/common/logging/src/async_record.rs b/common/logging/src/async_record.rs deleted file mode 100644 index 7a97fa1a75..0000000000 --- a/common/logging/src/async_record.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! An object that can be used to pass through a channel and be cloned. It can therefore be used -//! via the broadcast channel. - -use parking_lot::Mutex; -use serde::ser::SerializeMap; -use serde::serde_if_integer128; -use serde::Serialize; -use slog::{BorrowedKV, Key, Level, OwnedKVList, Record, RecordStatic, Serializer, SingleKV, KV}; -use std::cell::RefCell; -use std::fmt; -use std::fmt::Write; -use std::sync::Arc; -use take_mut::take; - -thread_local! { - static TL_BUF: RefCell = RefCell::new(String::with_capacity(128)) -} - -/// Serialized record. -#[derive(Clone)] -pub struct AsyncRecord { - msg: String, - level: Level, - location: Box, - tag: String, - logger_values: OwnedKVList, - kv: Arc>, -} - -impl AsyncRecord { - /// Serializes a `Record` and an `OwnedKVList`. - pub fn from(record: &Record, logger_values: &OwnedKVList) -> Self { - let mut ser = ToSendSerializer::new(); - record - .kv() - .serialize(record, &mut ser) - .expect("`ToSendSerializer` can't fail"); - - AsyncRecord { - msg: fmt::format(*record.msg()), - level: record.level(), - location: Box::new(*record.location()), - tag: String::from(record.tag()), - logger_values: logger_values.clone(), - kv: Arc::new(Mutex::new(ser.finish())), - } - } - - pub fn to_json_string(&self) -> Result { - serde_json::to_string(&self).map_err(|e| format!("{:?}", e)) - } -} - -pub struct ToSendSerializer { - kv: Box, -} - -impl ToSendSerializer { - fn new() -> Self { - ToSendSerializer { kv: Box::new(()) } - } - - fn finish(self) -> Box { - self.kv - } -} - -impl Serializer for ToSendSerializer { - fn emit_bool(&mut self, key: Key, val: bool) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_unit(&mut self, key: Key) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, ())))); - Ok(()) - } - fn emit_none(&mut self, key: Key) -> slog::Result { - let val: Option<()> = None; - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_char(&mut self, key: Key, val: char) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u8(&mut self, key: Key, val: u8) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i8(&mut self, key: Key, val: i8) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u16(&mut self, key: Key, val: u16) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i16(&mut self, key: Key, val: i16) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u32(&mut self, key: Key, val: u32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i32(&mut self, key: Key, val: i32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_f32(&mut self, key: Key, val: f32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u64(&mut self, key: Key, val: u64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i64(&mut self, key: Key, val: i64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_f64(&mut self, key: Key, val: f64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u128(&mut self, key: Key, val: u128) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i128(&mut self, key: Key, val: i128) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_usize(&mut self, key: Key, val: usize) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_isize(&mut self, key: Key, val: isize) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_str(&mut self, key: Key, val: &str) -> slog::Result { - let val = val.to_owned(); - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> slog::Result { - let val = fmt::format(*val); - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } -} - -impl Serialize for AsyncRecord { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Get the current time - let dt = chrono::Local::now().format("%b %e %T").to_string(); - - let rs = RecordStatic { - location: &self.location, - level: self.level, - tag: &self.tag, - }; - let mut map_serializer = SerdeSerializer::new(serializer)?; - - // Serialize the time and log level first - map_serializer.serialize_entry("time", &dt)?; - map_serializer.serialize_entry("level", self.level.as_short_str())?; - - let kv = self.kv.lock(); - - // Convoluted pattern to avoid binding `format_args!` to a temporary. - // See: https://stackoverflow.com/questions/56304313/cannot-use-format-args-due-to-temporary-value-is-freed-at-the-end-of-this-state - let mut f = |msg: std::fmt::Arguments| { - map_serializer.serialize_entry("msg", msg.to_string())?; - - let record = Record::new(&rs, &msg, BorrowedKV(&(*kv))); - self.logger_values - .serialize(&record, &mut map_serializer) - .map_err(serde::ser::Error::custom)?; - record - .kv() - .serialize(&record, &mut map_serializer) - .map_err(serde::ser::Error::custom) - }; - f(format_args!("{}", self.msg))?; - map_serializer.end() - } -} - -struct SerdeSerializer { - /// Current state of map serializing: `serde::Serializer::MapState` - ser_map: S::SerializeMap, -} - -impl SerdeSerializer { - fn new(ser: S) -> Result { - let ser_map = ser.serialize_map(None)?; - Ok(SerdeSerializer { ser_map }) - } - - fn serialize_entry(&mut self, key: K, value: V) -> Result<(), S::Error> - where - K: serde::Serialize, - V: serde::Serialize, - { - self.ser_map.serialize_entry(&key, &value) - } - - /// Finish serialization, and return the serializer - fn end(self) -> Result { - self.ser_map.end() - } -} - -// NOTE: This is borrowed from slog_json -macro_rules! impl_m( - ($s:expr, $key:expr, $val:expr) => ({ - let k_s: &str = $key.as_ref(); - $s.ser_map.serialize_entry(k_s, $val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("serde serialization error: {}", e)))?; - Ok(()) - }); -); - -impl slog::Serializer for SerdeSerializer -where - S: serde::Serializer, -{ - fn emit_bool(&mut self, key: Key, val: bool) -> slog::Result { - impl_m!(self, key, &val) - } - - fn emit_unit(&mut self, key: Key) -> slog::Result { - impl_m!(self, key, &()) - } - - fn emit_char(&mut self, key: Key, val: char) -> slog::Result { - impl_m!(self, key, &val) - } - - fn emit_none(&mut self, key: Key) -> slog::Result { - let val: Option<()> = None; - impl_m!(self, key, &val) - } - fn emit_u8(&mut self, key: Key, val: u8) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i8(&mut self, key: Key, val: i8) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u16(&mut self, key: Key, val: u16) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i16(&mut self, key: Key, val: i16) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_usize(&mut self, key: Key, val: usize) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_isize(&mut self, key: Key, val: isize) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u32(&mut self, key: Key, val: u32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i32(&mut self, key: Key, val: i32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_f32(&mut self, key: Key, val: f32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u64(&mut self, key: Key, val: u64) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i64(&mut self, key: Key, val: i64) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_f64(&mut self, key: Key, val: f64) -> slog::Result { - impl_m!(self, key, &val) - } - serde_if_integer128! { - fn emit_u128(&mut self, key: Key, val: u128) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i128(&mut self, key: Key, val: i128) -> slog::Result { - impl_m!(self, key, &val) - } - } - fn emit_str(&mut self, key: Key, val: &str) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> slog::Result { - TL_BUF.with(|buf| { - let mut buf = buf.borrow_mut(); - - buf.write_fmt(*val).unwrap(); - - let res = { || impl_m!(self, key, &*buf) }(); - buf.clear(); - res - }) - } -} diff --git a/common/logging/src/lib.rs b/common/logging/src/lib.rs index 0ddd867c2f..403f682a06 100644 --- a/common/logging/src/lib.rs +++ b/common/logging/src/lib.rs @@ -1,20 +1,20 @@ -use metrics::{inc_counter, try_create_int_counter, IntCounter, Result as MetricsResult}; -use slog::Logger; -use slog_term::Decorator; -use std::io::{Result, Write}; +use chrono::Local; +use logroller::{Compression, LogRollerBuilder, Rotation, RotationSize}; +use metrics::{try_create_int_counter, IntCounter, Result as MetricsResult}; +use std::io::Write; use std::path::PathBuf; use std::sync::LazyLock; use std::time::{Duration, Instant}; -use tracing_appender::non_blocking::NonBlocking; -use tracing_appender::rolling::{RollingFileAppender, Rotation}; -use tracing_logging_layer::LoggingLayer; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use tracing::Subscriber; +use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; +use tracing_subscriber::layer::Context; +use tracing_subscriber::{EnvFilter, Layer}; pub const MAX_MESSAGE_WIDTH: usize = 40; -pub mod async_record; +pub mod macros; mod sse_logging_components; -mod tracing_logging_layer; +pub mod tracing_logging_layer; mod tracing_metrics_layer; pub use sse_logging_components::SSELoggingComponents; @@ -32,169 +32,6 @@ pub static ERRORS_TOTAL: LazyLock> = pub static CRITS_TOTAL: LazyLock> = LazyLock::new(|| try_create_int_counter("crit_total", "Count of crits logged")); -pub struct AlignedTermDecorator { - wrapped: D, - message_width: usize, -} - -impl AlignedTermDecorator { - pub fn new(decorator: D, message_width: usize) -> Self { - AlignedTermDecorator { - wrapped: decorator, - message_width, - } - } -} - -impl Decorator for AlignedTermDecorator { - fn with_record( - &self, - record: &slog::Record, - _logger_values: &slog::OwnedKVList, - f: F, - ) -> Result<()> - where - F: FnOnce(&mut dyn slog_term::RecordDecorator) -> std::io::Result<()>, - { - match record.level() { - slog::Level::Info => inc_counter(&INFOS_TOTAL), - slog::Level::Warning => inc_counter(&WARNS_TOTAL), - slog::Level::Error => inc_counter(&ERRORS_TOTAL), - slog::Level::Critical => inc_counter(&CRITS_TOTAL), - _ => (), - } - - self.wrapped.with_record(record, _logger_values, |deco| { - f(&mut AlignedRecordDecorator::new(deco, self.message_width)) - }) - } -} - -struct AlignedRecordDecorator<'a> { - wrapped: &'a mut dyn slog_term::RecordDecorator, - message_count: usize, - message_active: bool, - ignore_comma: bool, - message_width: usize, -} - -impl<'a> AlignedRecordDecorator<'a> { - fn new( - decorator: &'a mut dyn slog_term::RecordDecorator, - message_width: usize, - ) -> AlignedRecordDecorator<'a> { - AlignedRecordDecorator { - wrapped: decorator, - message_count: 0, - ignore_comma: false, - message_active: false, - message_width, - } - } - - fn filtered_write(&mut self, buf: &[u8]) -> Result { - if self.ignore_comma { - //don't write comma - self.ignore_comma = false; - Ok(buf.len()) - } else if self.message_active { - self.wrapped.write(buf).inspect(|n| self.message_count += n) - } else { - self.wrapped.write(buf) - } - } -} - -impl Write for AlignedRecordDecorator<'_> { - fn write(&mut self, buf: &[u8]) -> Result { - if buf.iter().any(u8::is_ascii_control) { - let filtered = buf - .iter() - .cloned() - .map(|c| if !is_ascii_control(&c) { c } else { b'_' }) - .collect::>(); - self.filtered_write(&filtered) - } else { - self.filtered_write(buf) - } - } - - fn flush(&mut self) -> Result<()> { - self.wrapped.flush() - } -} - -impl slog_term::RecordDecorator for AlignedRecordDecorator<'_> { - fn reset(&mut self) -> Result<()> { - self.message_active = false; - self.message_count = 0; - self.ignore_comma = false; - self.wrapped.reset() - } - - fn start_whitespace(&mut self) -> Result<()> { - self.wrapped.start_whitespace() - } - - fn start_msg(&mut self) -> Result<()> { - self.message_active = true; - self.ignore_comma = false; - self.wrapped.start_msg() - } - - fn start_timestamp(&mut self) -> Result<()> { - self.wrapped.start_timestamp() - } - - fn start_level(&mut self) -> Result<()> { - self.wrapped.start_level() - } - - fn start_comma(&mut self) -> Result<()> { - if self.message_active && self.message_count + 1 < self.message_width { - self.ignore_comma = true; - } - self.wrapped.start_comma() - } - - fn start_key(&mut self) -> Result<()> { - if self.message_active && self.message_count + 1 < self.message_width { - write!( - self, - "{}", - " ".repeat(self.message_width - self.message_count) - )?; - self.message_active = false; - self.message_count = 0; - self.ignore_comma = false; - } - self.wrapped.start_key() - } - - fn start_value(&mut self) -> Result<()> { - self.wrapped.start_value() - } - - fn start_separator(&mut self) -> Result<()> { - self.wrapped.start_separator() - } -} - -/// Function to filter out ascii control codes. -/// -/// This helps to keep log formatting consistent. -/// Whitespace and padding control codes are excluded. -fn is_ascii_control(character: &u8) -> bool { - matches!( - character, - b'\x00'..=b'\x08' | - b'\x0b'..=b'\x0c' | - b'\x0e'..=b'\x1f' | - b'\x7f' | - b'\x81'..=b'\x9f' - ) -} - /// Provides de-bounce functionality for logging. #[derive(Default)] pub struct TimeLatch(Option); @@ -214,75 +51,133 @@ impl TimeLatch { } } -pub fn create_tracing_layer(base_tracing_log_path: PathBuf) { - let mut tracing_log_path = PathBuf::new(); +pub struct Libp2pDiscv5TracingLayer { + pub libp2p_non_blocking_writer: NonBlocking, + pub _libp2p_guard: WorkerGuard, + pub discv5_non_blocking_writer: NonBlocking, + pub _discv5_guard: WorkerGuard, +} - // Ensure that `tracing_log_path` only contains directories. - for p in base_tracing_log_path.iter() { - tracing_log_path = tracing_log_path.join(p); - if let Ok(metadata) = tracing_log_path.metadata() { - if !metadata.is_dir() { - tracing_log_path.pop(); - break; - } +impl Layer for Libp2pDiscv5TracingLayer +where + S: Subscriber, +{ + fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context) { + let meta = event.metadata(); + let log_level = meta.level(); + let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); + + let target = match meta.target().split_once("::") { + Some((crate_name, _)) => crate_name, + None => "unknown", + }; + + let mut writer = match target { + "gossipsub" => self.libp2p_non_blocking_writer.clone(), + "discv5" => self.discv5_non_blocking_writer.clone(), + _ => return, + }; + + let mut visitor = LogMessageExtractor { + message: String::default(), + }; + + event.record(&mut visitor); + let message = format!("{} {} {}\n", timestamp, log_level, visitor.message); + + if let Err(e) = writer.write_all(message.as_bytes()) { + eprintln!("Failed to write log: {}", e); } } - - let filter_layer = match tracing_subscriber::EnvFilter::try_from_default_env() - .or_else(|_| tracing_subscriber::EnvFilter::try_new("warn")) - { - Ok(filter) => filter, - Err(e) => { - eprintln!("Failed to initialize dependency logging {e}"); - return; - } - }; - - let Ok(libp2p_writer) = RollingFileAppender::builder() - .rotation(Rotation::DAILY) - .max_log_files(2) - .filename_prefix("libp2p") - .filename_suffix("log") - .build(tracing_log_path.clone()) - else { - eprintln!("Failed to initialize libp2p rolling file appender"); - return; - }; - - let Ok(discv5_writer) = RollingFileAppender::builder() - .rotation(Rotation::DAILY) - .max_log_files(2) - .filename_prefix("discv5") - .filename_suffix("log") - .build(tracing_log_path) - else { - eprintln!("Failed to initialize discv5 rolling file appender"); - return; - }; - - let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(libp2p_writer); - let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(discv5_writer); - - let custom_layer = LoggingLayer { - libp2p_non_blocking_writer, - _libp2p_guard, - discv5_non_blocking_writer, - _discv5_guard, - }; - - if let Err(e) = tracing_subscriber::fmt() - .with_env_filter(filter_layer) - .with_writer(std::io::sink) - .finish() - .with(MetricsLayer) - .with(custom_layer) - .try_init() - { - eprintln!("Failed to initialize dependency logging {e}"); - } } -/// Return a logger suitable for test usage. +struct LogMessageExtractor { + message: String, +} + +impl tracing_core::field::Visit for LogMessageExtractor { + fn record_debug(&mut self, _: &tracing_core::Field, value: &dyn std::fmt::Debug) { + self.message = format!("{} {:?}", self.message, value); + } +} + +pub fn create_libp2p_discv5_tracing_layer( + base_tracing_log_path: Option, + max_log_size: u64, + compression: bool, + max_log_number: usize, +) -> Libp2pDiscv5TracingLayer { + if let Some(mut tracing_log_path) = base_tracing_log_path { + // Ensure that `tracing_log_path` only contains directories. + for p in tracing_log_path.clone().iter() { + tracing_log_path = tracing_log_path.join(p); + if let Ok(metadata) = tracing_log_path.metadata() { + if !metadata.is_dir() { + tracing_log_path.pop(); + break; + } + } + } + + let mut libp2p_writer = + LogRollerBuilder::new(tracing_log_path.clone(), PathBuf::from("libp2p.log")) + .rotation(Rotation::SizeBased(RotationSize::MB(max_log_size))) + .max_keep_files(max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + let mut discv5_writer = + LogRollerBuilder::new(tracing_log_path.clone(), PathBuf::from("discv5.log")) + .rotation(Rotation::SizeBased(RotationSize::MB(max_log_size))) + .max_keep_files(max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + if compression { + libp2p_writer = libp2p_writer.compression(Compression::Gzip); + discv5_writer = discv5_writer.compression(Compression::Gzip); + } + + let libp2p_writer = match libp2p_writer.build() { + Ok(writer) => writer, + Err(e) => { + eprintln!("Failed to initialize libp2p rolling file appender: {e}"); + std::process::exit(1); + } + }; + + let discv5_writer = match discv5_writer.build() { + Ok(writer) => writer, + Err(e) => { + eprintln!("Failed to initialize discv5 rolling file appender: {e}"); + std::process::exit(1); + } + }; + + let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(libp2p_writer); + let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(discv5_writer); + + Libp2pDiscv5TracingLayer { + libp2p_non_blocking_writer, + _libp2p_guard, + discv5_non_blocking_writer, + _discv5_guard, + } + } else { + let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(std::io::sink()); + let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(std::io::sink()); + Libp2pDiscv5TracingLayer { + libp2p_non_blocking_writer, + _libp2p_guard, + discv5_non_blocking_writer, + _discv5_guard, + } + } +} + +/// Return a tracing subscriber suitable for test usage. /// /// By default no logs will be printed, but they can be enabled via /// the `test_logger` feature. This feature can be enabled for any @@ -290,17 +185,10 @@ pub fn create_tracing_layer(base_tracing_log_path: PathBuf) { /// ```bash /// cargo test -p beacon_chain --features logging/test_logger /// ``` -pub fn test_logger() -> Logger { - use sloggers::Build; - +pub fn create_test_tracing_subscriber() { if cfg!(feature = "test_logger") { - sloggers::terminal::TerminalLoggerBuilder::new() - .level(sloggers::types::Severity::Debug) - .build() - .expect("Should build TerminalLoggerBuilder") - } else { - sloggers::null::NullLoggerBuilder - .build() - .expect("Should build NullLoggerBuilder") + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new("debug").unwrap()) + .try_init(); } } diff --git a/common/logging/src/macros.rs b/common/logging/src/macros.rs new file mode 100644 index 0000000000..eb25eba56c --- /dev/null +++ b/common/logging/src/macros.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! crit { + ($($arg:tt)*) => { + tracing::error!(error_type = "crit", $($arg)*); + }; +} diff --git a/common/logging/src/sse_logging_components.rs b/common/logging/src/sse_logging_components.rs index 244d09fbd1..a25b5be6c5 100644 --- a/common/logging/src/sse_logging_components.rs +++ b/common/logging/src/sse_logging_components.rs @@ -1,46 +1,109 @@ +// TODO(tracing) fix the comments below and remove reference of slog::Drain //! This module provides an implementation of `slog::Drain` that optionally writes to a channel if //! there are subscribers to a HTTP SSE stream. -use crate::async_record::AsyncRecord; -use slog::{Drain, OwnedKVList, Record}; -use std::panic::AssertUnwindSafe; +use serde_json::json; +use serde_json::Value; use std::sync::Arc; use tokio::sync::broadcast::Sender; +use tracing::field::{Field, Visit}; +use tracing::{Event, Subscriber}; +use tracing_subscriber::layer::{Context, Layer}; /// Default log level for SSE Events. // NOTE: Made this a constant. Debug level seems to be pretty intense. Can make this // configurable later if needed. -const LOG_LEVEL: slog::Level = slog::Level::Info; +const LOG_LEVEL: tracing::Level = tracing::Level::INFO; /// The components required in the HTTP API task to receive logged events. #[derive(Clone)] pub struct SSELoggingComponents { /// The channel to receive events from. - pub sender: Arc>>, + pub sender: Arc>>, } impl SSELoggingComponents { - /// Create a new SSE drain. pub fn new(channel_size: usize) -> Self { let (sender, _receiver) = tokio::sync::broadcast::channel(channel_size); - let sender = Arc::new(AssertUnwindSafe(sender)); - SSELoggingComponents { sender } + SSELoggingComponents { + sender: Arc::new(sender), + } } } -impl Drain for SSELoggingComponents { - type Ok = (); - type Err = &'static str; +impl Layer for SSELoggingComponents { + fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { + if *event.metadata().level() > LOG_LEVEL { + return; + } - fn log(&self, record: &Record, logger_values: &OwnedKVList) -> Result { - if record.level().is_at_least(LOG_LEVEL) { - // Attempt to send the logs - match self.sender.send(AsyncRecord::from(record, logger_values)) { - Ok(_num_sent) => {} // Everything got sent - Err(_err) => {} // There are no subscribers, do nothing + let mut visitor = TracingEventVisitor::new(); + event.record(&mut visitor); + let mut log_entry = visitor.finish(event.metadata()); + + if let Some(error_type) = log_entry + .get("fields") + .and_then(|fields| fields.get("error_type")) + .and_then(|val| val.as_str()) + { + if error_type.eq_ignore_ascii_case("crit") { + log_entry["level"] = json!("CRIT"); + + if let Some(Value::Object(ref mut map)) = log_entry.get_mut("fields") { + map.remove("error_type"); + } } } - Ok(()) + + let _ = self.sender.send(Arc::new(log_entry)); + } +} +struct TracingEventVisitor { + fields: serde_json::Map, +} + +impl TracingEventVisitor { + fn new() -> Self { + TracingEventVisitor { + fields: serde_json::Map::new(), + } + } + + fn finish(self, metadata: &tracing::Metadata<'_>) -> Value { + let mut log_entry = serde_json::Map::new(); + log_entry.insert( + "time".to_string(), + json!(chrono::Local::now() + .format("%b %d %H:%M:%S%.3f") + .to_string()), + ); + log_entry.insert("level".to_string(), json!(metadata.level().to_string())); + log_entry.insert("target".to_string(), json!(metadata.target())); + log_entry.insert("fields".to_string(), Value::Object(self.fields)); + Value::Object(log_entry) + } +} + +impl Visit for TracingEventVisitor { + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + self.fields + .insert(field.name().to_string(), json!(format!("{:?}", value))); + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields.insert(field.name().to_string(), json!(value)); } } diff --git a/common/logging/src/tracing_logging_layer.rs b/common/logging/src/tracing_logging_layer.rs index a9ddae828a..4478e1facb 100644 --- a/common/logging/src/tracing_logging_layer.rs +++ b/common/logging/src/tracing_logging_layer.rs @@ -1,56 +1,531 @@ use chrono::prelude::*; +use serde_json::{Map, Value}; +use std::collections::HashMap; use std::io::Write; +use std::sync::{Arc, Mutex}; +use tracing::field::Field; +use tracing::span::Id; use tracing::Subscriber; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; use tracing_subscriber::layer::Context; +use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::Layer; pub struct LoggingLayer { - pub libp2p_non_blocking_writer: NonBlocking, - pub _libp2p_guard: WorkerGuard, - pub discv5_non_blocking_writer: NonBlocking, - pub _discv5_guard: WorkerGuard, + pub non_blocking_writer: NonBlocking, + pub guard: WorkerGuard, + pub disable_log_timestamp: bool, + pub log_color: bool, + pub logfile_color: bool, + pub log_format: Option, + pub logfile_format: Option, + pub extra_info: bool, + pub dep_logs: bool, + span_fields: Arc>>, +} + +impl LoggingLayer { + #[allow(clippy::too_many_arguments)] + pub fn new( + non_blocking_writer: NonBlocking, + guard: WorkerGuard, + disable_log_timestamp: bool, + log_color: bool, + logfile_color: bool, + log_format: Option, + logfile_format: Option, + extra_info: bool, + dep_logs: bool, + ) -> Self { + Self { + non_blocking_writer, + guard, + disable_log_timestamp, + log_color, + logfile_color, + log_format, + logfile_format, + extra_info, + dep_logs, + span_fields: Arc::new(Mutex::new(HashMap::new())), + } + } } impl Layer for LoggingLayer where - S: Subscriber, + S: Subscriber + for<'a> LookupSpan<'a>, { - fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context) { + fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, _ctx: Context) { + let metadata = attrs.metadata(); + let span_name = metadata.name(); + + let mut visitor = SpanFieldsExtractor::default(); + attrs.record(&mut visitor); + + let span_data = SpanData { + name: span_name.to_string(), + fields: visitor.fields, + }; + + let mut span_fields = match self.span_fields.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + span_fields.insert(id.clone(), span_data); + } + + fn on_event(&self, event: &tracing::Event<'_>, ctx: Context) { let meta = event.metadata(); let log_level = meta.level(); - let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); - - let target = match meta.target().split_once("::") { - Some((crate_name, _)) => crate_name, - None => "unknown", + let timestamp = if !self.disable_log_timestamp { + Local::now().format("%b %d %H:%M:%S%.3f").to_string() + } else { + String::new() }; - let mut writer = match target { - "gossipsub" => self.libp2p_non_blocking_writer.clone(), - "discv5" => self.discv5_non_blocking_writer.clone(), - _ => return, - }; + if !self.dep_logs { + if let Some(file) = meta.file() { + if file.contains("/.cargo/") { + return; + } + } else { + return; + } + } + + let mut writer = self.non_blocking_writer.clone(); let mut visitor = LogMessageExtractor { - message: String::default(), + message: String::new(), + fields: Vec::new(), + is_crit: false, + }; + event.record(&mut visitor); + + // Remove ascii control codes from message. + // All following formatting and logs components are predetermined or known. + if visitor.message.as_bytes().iter().any(u8::is_ascii_control) { + let filtered = visitor + .message + .as_bytes() + .iter() + .map(|c| if is_ascii_control(c) { b'_' } else { *c }) + .collect::>(); + visitor.message = String::from_utf8(filtered).unwrap_or_default(); }; - event.record(&mut visitor); - let message = format!("{} {} {}\n", timestamp, log_level, visitor.message); + let module = meta.module_path().unwrap_or(""); + let file = meta.file().unwrap_or(""); + let line = match meta.line() { + Some(line) => line.to_string(), + None => "".to_string(), + }; - if let Err(e) = writer.write_all(message.as_bytes()) { - eprintln!("Failed to write log: {}", e); + if module.contains("discv5") { + visitor + .fields + .push(("service".to_string(), "\"discv5\"".to_string())); } + + let gray = "\x1b[90m"; + let reset = "\x1b[0m"; + let location = if self.extra_info { + if self.logfile_color { + format!("{}{}::{}:{}{}", gray, module, file, line, reset) + } else { + format!("{}::{}:{}", module, file, line) + } + } else { + String::new() + }; + + let plain_level_str = if visitor.is_crit { + "CRIT" + } else { + match *log_level { + tracing::Level::ERROR => "ERROR", + tracing::Level::WARN => "WARN", + tracing::Level::INFO => "INFO", + tracing::Level::DEBUG => "DEBUG", + tracing::Level::TRACE => "TRACE", + } + }; + + let color_level_str = if visitor.is_crit { + "\x1b[35mCRIT\x1b[0m" + } else { + match *log_level { + tracing::Level::ERROR => "\x1b[31mERROR\x1b[0m", + tracing::Level::WARN => "\x1b[33mWARN\x1b[0m", + tracing::Level::INFO => "\x1b[32mINFO\x1b[0m", + tracing::Level::DEBUG => "\x1b[34mDEBUG\x1b[0m", + tracing::Level::TRACE => "\x1b[35mTRACE\x1b[0m", + } + }; + + if self.dep_logs { + if self.logfile_format.as_deref() == Some("JSON") { + build_json_log_file( + &visitor, + plain_level_str, + meta, + &ctx, + &self.span_fields, + event, + &mut writer, + ); + } else { + build_log_text( + &visitor, + plain_level_str, + ×tamp, + &ctx, + &self.span_fields, + event, + &location, + color_level_str, + self.logfile_color, + &mut writer, + ); + } + } else if self.log_format.as_deref() == Some("JSON") { + build_json_log_stdout(&visitor, plain_level_str, ×tamp, &mut writer); + } else { + build_log_text( + &visitor, + plain_level_str, + ×tamp, + &ctx, + &self.span_fields, + event, + &location, + color_level_str, + self.log_color, + &mut writer, + ); + } + } +} + +struct SpanData { + name: String, + fields: Vec<(String, String)>, +} + +#[derive(Default)] +struct SpanFieldsExtractor { + fields: Vec<(String, String)>, +} + +impl tracing_core::field::Visit for SpanFieldsExtractor { + fn record_str(&mut self, field: &Field, value: &str) { + self.fields + .push((field.name().to_string(), format!("\"{}\"", value))); + } + + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + self.fields + .push((field.name().to_string(), format!("{:?}", value))); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields + .push((field.name().to_string(), value.to_string())); } } struct LogMessageExtractor { message: String, + fields: Vec<(String, String)>, + is_crit: bool, } impl tracing_core::field::Visit for LogMessageExtractor { - fn record_debug(&mut self, _: &tracing_core::Field, value: &dyn std::fmt::Debug) { - self.message = format!("{} {:?}", self.message, value); + fn record_str(&mut self, field: &Field, value: &str) { + if field.name() == "message" { + if self.message.is_empty() { + self.message = value.to_string(); + } else { + self.fields + .push(("msg_id".to_string(), format!("\"{}\"", value))); + } + } else if field.name() == "error_type" && value == "crit" { + self.is_crit = true; + } else { + self.fields + .push((field.name().to_string(), format!("\"{}\"", value))); + } + } + + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + if field.name() == "message" { + if self.message.is_empty() { + self.message = format!("{:?}", value); + } else { + self.fields + .push(("msg_id".to_string(), format!("{:?}", value))); + } + } else if field.name() == "error_type" && format!("{:?}", value) == "\"crit\"" { + self.is_crit = true; + } else { + self.fields + .push((field.name().to_string(), format!("{:?}", value))); + } + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields + .push((field.name().to_string(), value.to_string())); } } + +/// Function to filter out ascii control codes. +/// +/// This helps to keep log formatting consistent. +/// Whitespace and padding control codes are excluded. +fn is_ascii_control(character: &u8) -> bool { + matches!( + character, + b'\x00'..=b'\x08' | + b'\x0b'..=b'\x0c' | + b'\x0e'..=b'\x1f' | + b'\x7f' | + b'\x81'..=b'\x9f' + ) +} + +fn build_json_log_stdout( + visitor: &LogMessageExtractor, + plain_level_str: &str, + timestamp: &str, + writer: &mut impl Write, +) { + let mut log_map = Map::new(); + log_map.insert("msg".to_string(), Value::String(visitor.message.clone())); + log_map.insert( + "level".to_string(), + Value::String(plain_level_str.to_string()), + ); + log_map.insert("ts".to_string(), Value::String(timestamp.to_string())); + + for (key, val) in visitor.fields.clone().into_iter() { + let parsed_val = parse_field(&val); + log_map.insert(key, parsed_val); + } + + let json_obj = Value::Object(log_map); + let output = format!("{}\n", json_obj); + + if let Err(e) = writer.write_all(output.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +fn build_json_log_file<'a, S>( + visitor: &LogMessageExtractor, + plain_level_str: &str, + meta: &tracing::Metadata<'_>, + ctx: &Context<'_, S>, + span_fields: &Arc>>, + event: &tracing::Event<'_>, + writer: &mut impl Write, +) where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, +{ + let utc_timestamp = Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true); + let mut log_map = Map::new(); + + log_map.insert("msg".to_string(), Value::String(visitor.message.clone())); + log_map.insert( + "level".to_string(), + Value::String(plain_level_str.to_string()), + ); + log_map.insert("ts".to_string(), Value::String(utc_timestamp)); + + let module_path = meta.module_path().unwrap_or(""); + let line_number = meta + .line() + .map_or("".to_string(), |l| l.to_string()); + let module_field = format!("{}:{}", module_path, line_number); + log_map.insert("module".to_string(), Value::String(module_field)); + + for (key, val) in visitor.fields.clone().into_iter() { + let cleaned_value = if val.starts_with('\"') && val.ends_with('\"') && val.len() >= 2 { + &val[1..val.len() - 1] + } else { + &val + }; + let parsed_val = + serde_json::from_str(cleaned_value).unwrap_or(Value::String(cleaned_value.to_string())); + log_map.insert(key, parsed_val); + } + + if let Some(scope) = ctx.event_scope(event) { + let guard = span_fields.lock().ok(); + if let Some(span_map) = guard { + for span in scope { + let id = span.id(); + if let Some(span_data) = span_map.get(&id) { + for (key, val) in &span_data.fields { + let parsed_span_val = parse_field(val); + log_map.insert(key.clone(), parsed_span_val); + } + } + } + } + } + + let json_obj = Value::Object(log_map); + let output = format!("{}\n", json_obj); + + if let Err(e) = writer.write_all(output.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +#[allow(clippy::too_many_arguments)] +fn build_log_text<'a, S>( + visitor: &LogMessageExtractor, + plain_level_str: &str, + timestamp: &str, + ctx: &Context<'_, S>, + span_fields: &Arc>>, + event: &tracing::Event<'_>, + location: &str, + color_level_str: &str, + use_color: bool, + writer: &mut impl Write, +) where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, +{ + let bold_start = "\x1b[1m"; + let bold_end = "\x1b[0m"; + let mut collected_span_fields = Vec::new(); + + if let Some(scope) = ctx.event_scope(event) { + for span in scope { + let id = span.id(); + let span_fields_map = span_fields.lock().unwrap(); + if let Some(span_data) = span_fields_map.get(&id) { + collected_span_fields.push((span_data.name.clone(), span_data.fields.clone())); + } + } + } + + let mut formatted_spans = String::new(); + for (_, fields) in collected_span_fields.iter().rev() { + for (i, (field_name, field_value)) in fields.iter().enumerate() { + if i > 0 && !visitor.fields.is_empty() { + formatted_spans.push_str(", "); + } + if use_color { + formatted_spans.push_str(&format!( + "{}{}{}: {}", + bold_start, field_name, bold_end, field_value + )); + } else { + formatted_spans.push_str(&format!("{}: {}", field_name, field_value)); + } + } + } + + let level_str = if use_color { + color_level_str + } else { + plain_level_str + }; + + let fixed_message_width = 44; + let message_len = visitor.message.len(); + + let message_content = if use_color { + format!("{}{}{}", bold_start, visitor.message, bold_end) + } else { + visitor.message.clone() + }; + + let padded_message = if message_len < fixed_message_width { + let extra_color_len = if use_color { + bold_start.len() + bold_end.len() + } else { + 0 + }; + format!( + "{: 0 { + formatted_fields.push_str(", "); + } + if use_color { + formatted_fields.push_str(&format!( + "{}{}{}: {}", + bold_start, field_name, bold_end, field_value + )); + } else { + formatted_fields.push_str(&format!("{}: {}", field_name, field_value)); + } + if i == visitor.fields.len() - 1 && !collected_span_fields.is_empty() { + formatted_fields.push(','); + } + } + + let full_message = if !formatted_fields.is_empty() { + format!("{} {}", padded_message, formatted_fields) + } else { + padded_message.to_string() + }; + + let message = if !location.is_empty() { + format!( + "{} {} {} {} {}\n", + timestamp, level_str, location, full_message, formatted_spans + ) + } else { + format!( + "{} {} {} {}\n", + timestamp, level_str, full_message, formatted_spans + ) + }; + + if let Err(e) = writer.write_all(message.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +fn parse_field(val: &str) -> Value { + let cleaned = if val.starts_with('"') && val.ends_with('"') && val.len() >= 2 { + &val[1..val.len() - 1] + } else { + val + }; + serde_json::from_str(cleaned).unwrap_or(Value::String(cleaned.to_string())) +} diff --git a/common/logging/tests/test.rs b/common/logging/tests/test.rs deleted file mode 100644 index f39f2b6d5a..0000000000 --- a/common/logging/tests/test.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::env; -use std::process::Command; -use std::process::Output; - -fn run_cmd(cmd_line: &str) -> Result { - if cfg!(target_os = "windows") { - Command::new(r#"cmd"#).args(["/C", cmd_line]).output() - } else { - Command::new(r#"sh"#).args(["-c", cmd_line]).output() - } -} - -#[test] -fn test_test_logger_with_feature_test_logger() { - let cur_dir = env::current_dir().unwrap(); - let test_dir = cur_dir - .join("..") - .join("..") - .join("testing") - .join("test-test_logger"); - let cmd_line = format!( - "cd {} && cargo test --features logging/test_logger", - test_dir.to_str().unwrap() - ); - - let output = run_cmd(&cmd_line); - - // Assert output data DOES contain "INFO hi, " - let data = String::from_utf8(output.unwrap().stderr).unwrap(); - println!("data={}", data); - assert!(data.contains("INFO hi, ")); -} - -#[test] -fn test_test_logger_no_features() { - // Test without features - let cur_dir = env::current_dir().unwrap(); - let test_dir = cur_dir - .join("..") - .join("..") - .join("testing") - .join("test-test_logger"); - let cmd_line = format!("cd {} && cargo test", test_dir.to_str().unwrap()); - - let output = run_cmd(&cmd_line); - - // Assert output data DOES contain "INFO hi, " - let data = String::from_utf8(output.unwrap().stderr).unwrap(); - println!("data={}", data); - assert!(!data.contains("INFO hi, ")); -} diff --git a/common/malloc_utils/src/jemalloc.rs b/common/malloc_utils/src/jemalloc.rs index f3a35fc41c..2e90c0ddf3 100644 --- a/common/malloc_utils/src/jemalloc.rs +++ b/common/malloc_utils/src/jemalloc.rs @@ -7,9 +7,11 @@ //! //! A) `JEMALLOC_SYS_WITH_MALLOC_CONF` at compile-time. //! B) `_RJEM_MALLOC_CONF` at runtime. -use metrics::{set_gauge, try_create_int_gauge, IntGauge}; +use metrics::{ + set_gauge, set_gauge_vec, try_create_int_gauge, try_create_int_gauge_vec, IntGauge, IntGaugeVec, +}; use std::sync::LazyLock; -use tikv_jemalloc_ctl::{arenas, epoch, stats, Access, AsName, Error}; +use tikv_jemalloc_ctl::{arenas, epoch, raw, stats, Access, AsName, Error}; #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -33,6 +35,38 @@ pub static BYTES_RESIDENT: LazyLock> = LazyLock::new(| pub static BYTES_RETAINED: LazyLock> = LazyLock::new(|| { try_create_int_gauge("jemalloc_bytes_retained", "Equivalent to stats.retained") }); +pub static JEMALLOC_ARENAS_SMALL_NMALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_small_nmalloc", + "Equivalent to stats.arenas..small.nmalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_SMALL_NDALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_small_ndalloc", + "Equivalent to stats.arenas..small.ndalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_LARGE_NMALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_large_nmalloc", + "Equivalent to stats.arenas..large.nmalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_LARGE_NDALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_large_ndalloc", + "Equivalent to stats.arenas..large.ndalloc", + &["arena"], + ) + }); pub fn scrape_jemalloc_metrics() { scrape_jemalloc_metrics_fallible().unwrap() @@ -42,7 +76,8 @@ pub fn scrape_jemalloc_metrics_fallible() -> Result<(), Error> { // Advance the epoch so that the underlying statistics are updated. epoch::advance()?; - set_gauge(&NUM_ARENAS, arenas::narenas::read()? as i64); + let num_arenas = arenas::narenas::read()?; + set_gauge(&NUM_ARENAS, num_arenas as i64); set_gauge(&BYTES_ALLOCATED, stats::allocated::read()? as i64); set_gauge(&BYTES_ACTIVE, stats::active::read()? as i64); set_gauge(&BYTES_MAPPED, stats::mapped::read()? as i64); @@ -50,9 +85,40 @@ pub fn scrape_jemalloc_metrics_fallible() -> Result<(), Error> { set_gauge(&BYTES_RESIDENT, stats::resident::read()? as i64); set_gauge(&BYTES_RETAINED, stats::retained::read()? as i64); + for arena in 0..num_arenas { + unsafe { + set_stats_gauge( + &JEMALLOC_ARENAS_SMALL_NMALLOC, + arena, + &format!("stats.arenas.{arena}.small.nmalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_SMALL_NDALLOC, + arena, + &format!("stats.arenas.{arena}.small.ndalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_LARGE_NMALLOC, + arena, + &format!("stats.arenas.{arena}.large.nmalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_LARGE_NDALLOC, + arena, + &format!("stats.arenas.{arena}.large.ndalloc\0"), + ); + } + } + Ok(()) } +unsafe fn set_stats_gauge(metric: &metrics::Result, arena: u32, stat: &str) { + if let Ok(val) = raw::read::(stat.as_bytes()) { + set_gauge_vec(metric, &[&format!("arena_{arena}")], val as i64); + } +} + pub fn page_size() -> Result { // Full list of keys: https://jemalloc.net/jemalloc.3.html "arenas.page\0".name().read() diff --git a/common/monitoring_api/Cargo.toml b/common/monitoring_api/Cargo.toml index cb52cff29a..9e2c36e2c7 100644 --- a/common/monitoring_api/Cargo.toml +++ b/common/monitoring_api/Cargo.toml @@ -15,7 +15,7 @@ reqwest = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } store = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } diff --git a/common/monitoring_api/src/lib.rs b/common/monitoring_api/src/lib.rs index 6f919971b0..966a1a3054 100644 --- a/common/monitoring_api/src/lib.rs +++ b/common/monitoring_api/src/lib.rs @@ -9,9 +9,9 @@ use reqwest::{IntoUrl, Response}; pub use reqwest::{StatusCode, Url}; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info}; use task_executor::TaskExecutor; use tokio::time::{interval_at, Instant}; +use tracing::{debug, error, info}; use types::*; pub use types::ProcessType; @@ -69,11 +69,10 @@ pub struct MonitoringHttpClient { freezer_db_path: Option, update_period: Duration, monitoring_endpoint: SensitiveUrl, - log: slog::Logger, } impl MonitoringHttpClient { - pub fn new(config: &Config, log: slog::Logger) -> Result { + pub fn new(config: &Config) -> Result { Ok(Self { client: reqwest::Client::new(), db_path: config.db_path.clone(), @@ -83,7 +82,6 @@ impl MonitoringHttpClient { ), monitoring_endpoint: SensitiveUrl::parse(&config.monitoring_endpoint) .map_err(|e| format!("Invalid monitoring endpoint: {:?}", e))?, - log, }) } @@ -111,10 +109,9 @@ impl MonitoringHttpClient { ); info!( - self.log, - "Starting monitoring API"; - "endpoint" => %self.monitoring_endpoint, - "update_period" => format!("{}s", self.update_period.as_secs()), + endpoint = %self.monitoring_endpoint, + update_period = format!("{}s", self.update_period.as_secs()), + "Starting monitoring API" ); let update_future = async move { @@ -122,10 +119,10 @@ impl MonitoringHttpClient { interval.tick().await; match self.send_metrics(&processes).await { Ok(()) => { - debug!(self.log, "Metrics sent to remote server"; "endpoint" => %self.monitoring_endpoint); + debug!(endpoint = %self.monitoring_endpoint, "Metrics sent to remote server"); } Err(e) => { - error!(self.log, "Failed to send metrics to remote endpoint"; "error" => %e) + error!(error = %e, "Failed to send metrics to remote endpoint") } } } @@ -187,18 +184,16 @@ impl MonitoringHttpClient { for process in processes { match self.get_metrics(process).await { Err(e) => error!( - self.log, - "Failed to get metrics"; - "process_type" => ?process, - "error" => %e + process_type = ?process, + error = %e, + "Failed to get metrics" ), Ok(metric) => metrics.push(metric), } } info!( - self.log, - "Sending metrics to remote endpoint"; - "endpoint" => %self.monitoring_endpoint + endpoint = %self.monitoring_endpoint, + "Sending metrics to remote endpoint" ); self.post(self.monitoring_endpoint.full.clone(), &metrics) .await diff --git a/common/task_executor/Cargo.toml b/common/task_executor/Cargo.toml index c1ac4b55a9..4224f00acc 100644 --- a/common/task_executor/Cargo.toml +++ b/common/task_executor/Cargo.toml @@ -4,17 +4,9 @@ version = "0.1.0" authors = ["Sigma Prime "] edition = { workspace = true } -[features] -default = ["slog"] -slog = ["dep:slog", "dep:sloggers", "dep:logging"] -tracing = ["dep:tracing"] - [dependencies] async-channel = { workspace = true } futures = { workspace = true } -logging = { workspace = true, optional = true } metrics = { workspace = true } -slog = { workspace = true, optional = true } -sloggers = { workspace = true, optional = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tracing = { workspace = true, optional = true } +tracing = { workspace = true } diff --git a/common/task_executor/src/lib.rs b/common/task_executor/src/lib.rs index 92ddb7c0be..dbdac600f3 100644 --- a/common/task_executor/src/lib.rs +++ b/common/task_executor/src/lib.rs @@ -1,20 +1,14 @@ mod metrics; -#[cfg(not(feature = "tracing"))] pub mod test_utils; use futures::channel::mpsc::Sender; use futures::prelude::*; use std::sync::Weak; use tokio::runtime::{Handle, Runtime}; +use tracing::{debug, instrument}; pub use tokio::task::JoinHandle; -// Set up logging framework -#[cfg(not(feature = "tracing"))] -use slog::{debug, o}; -#[cfg(feature = "tracing")] -use tracing::debug; - /// Provides a reason when Lighthouse is shut down. #[derive(Copy, Clone, Debug, PartialEq)] pub enum ShutdownReason { @@ -85,8 +79,9 @@ pub struct TaskExecutor { /// /// The task must provide a reason for shutting down. signal_tx: Sender, - #[cfg(not(feature = "tracing"))] - log: slog::Logger, + + /// The name of the service for inclusion in the logger output. + service_name: String, } impl TaskExecutor { @@ -97,39 +92,29 @@ impl TaskExecutor { /// This function should only be used during testing. In production, prefer to obtain an /// instance of `Self` via a `environment::RuntimeContext` (see the `lighthouse/environment` /// crate). + #[instrument(parent = None,level = "info", fields(service = service_name), name = "task_executor", skip_all)] pub fn new>( handle: T, exit: async_channel::Receiver<()>, - #[cfg(not(feature = "tracing"))] log: slog::Logger, signal_tx: Sender, + service_name: String, ) -> Self { Self { handle_provider: handle.into(), exit, signal_tx, - #[cfg(not(feature = "tracing"))] - log, + service_name, } } /// Clones the task executor adding a service name. - #[cfg(not(feature = "tracing"))] + #[instrument(parent = None,level = "info", fields(service = service_name), name = "task_executor", skip_all)] pub fn clone_with_name(&self, service_name: String) -> Self { TaskExecutor { handle_provider: self.handle_provider.clone(), exit: self.exit.clone(), signal_tx: self.signal_tx.clone(), - log: self.log.new(o!("service" => service_name)), - } - } - - /// Clones the task executor adding a service name. - #[cfg(feature = "tracing")] - pub fn clone(&self) -> Self { - TaskExecutor { - handle_provider: self.handle_provider.clone(), - exit: self.exit.clone(), - signal_tx: self.signal_tx.clone(), + service_name, } } @@ -139,6 +124,7 @@ impl TaskExecutor { /// The purpose of this function is to create a compile error if some function which previously /// returned `()` starts returning something else. Such a case may otherwise result in /// accidental error suppression. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_ignoring_error( &self, task: impl Future> + Send + 'static, @@ -150,6 +136,7 @@ impl TaskExecutor { /// Spawn a task to monitor the completion of another task. /// /// If the other task exits by panicking, then the monitor task will shut down the executor. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] fn spawn_monitor( &self, task_handle: impl Future> + Send + 'static, @@ -168,13 +155,7 @@ impl TaskExecutor { drop(timer); }); } else { - #[cfg(not(feature = "tracing"))] - debug!( - self.log, - "Couldn't spawn monitor task. Runtime shutting down" - ); - #[cfg(feature = "tracing")] - debug!("Couldn't spawn monitor task. Runtime shutting down"); + debug!("Couldn't spawn monitor task. Runtime shutting down") } } @@ -187,6 +168,7 @@ impl TaskExecutor { /// of a panic, the executor will be shut down via `self.signal_tx`. /// /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn(&self, task: impl Future + Send + 'static, name: &'static str) { if let Some(task_handle) = self.spawn_handle(task, name) { self.spawn_monitor(task_handle, name) @@ -202,6 +184,7 @@ impl TaskExecutor { /// This is useful in cases where the future to be spawned needs to do additional cleanup work when /// the task is completed/canceled (e.g. writing local variables to disk) or the task is created from /// some framework which does its own cleanup (e.g. a hyper server). + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_without_exit( &self, task: impl Future + Send + 'static, @@ -218,9 +201,6 @@ impl TaskExecutor { if let Some(handle) = self.handle() { handle.spawn(future); } else { - #[cfg(not(feature = "tracing"))] - debug!(self.log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); } } @@ -242,16 +222,13 @@ impl TaskExecutor { /// The task is cancelled when the corresponding async-channel is dropped. /// /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_handle( &self, task: impl Future + Send + 'static, name: &'static str, ) -> Option>> { let exit = self.exit(); - - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); - if let Some(int_gauge) = metrics::get_int_gauge(&metrics::ASYNC_TASKS_COUNT, &[name]) { // Task is shutdown before it completes if `exit` receives let int_gauge_1 = int_gauge.clone(); @@ -262,9 +239,6 @@ impl TaskExecutor { let result = match future::select(Box::pin(task), exit).await { future::Either::Left((value, _)) => Some(value), future::Either::Right(_) => { - #[cfg(not(feature = "tracing"))] - debug!(log, "Async task shutdown, exit received"; "task" => name); - #[cfg(feature = "tracing")] debug!(task = name, "Async task shutdown, exit received"); None } @@ -273,9 +247,6 @@ impl TaskExecutor { result })) } else { - #[cfg(not(feature = "tracing"))] - debug!(log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); None } @@ -290,6 +261,7 @@ impl TaskExecutor { /// The Future returned behaves like the standard JoinHandle which can return an error if the /// task failed. /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_blocking_handle( &self, task: F, @@ -299,18 +271,12 @@ impl TaskExecutor { F: FnOnce() -> R + Send + 'static, R: Send + 'static, { - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); - let timer = metrics::start_timer_vec(&metrics::BLOCKING_TASKS_HISTOGRAM, &[name]); metrics::inc_gauge_vec(&metrics::BLOCKING_TASKS_COUNT, &[name]); let join_handle = if let Some(handle) = self.handle() { handle.spawn_blocking(task) } else { - #[cfg(not(feature = "tracing"))] - debug!(self.log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); return None; }; @@ -319,9 +285,6 @@ impl TaskExecutor { let result = match join_handle.await { Ok(result) => Ok(result), Err(error) => { - #[cfg(not(feature = "tracing"))] - debug!(log, "Blocking task ended unexpectedly"; "error" => %error); - #[cfg(feature = "tracing")] debug!(%error, "Blocking task ended unexpectedly"); Err(error) } @@ -347,6 +310,7 @@ impl TaskExecutor { /// a `tokio` context present in the thread-local storage due to some `rayon` funkiness. Talk to /// @paulhauner if you plan to use this function in production. He has put metrics in here to /// track any use of it, so don't think you can pull a sneaky one on him. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn block_on_dangerous( &self, future: F, @@ -354,44 +318,20 @@ impl TaskExecutor { ) -> Option { let timer = metrics::start_timer_vec(&metrics::BLOCK_ON_TASKS_HISTOGRAM, &[name]); metrics::inc_gauge_vec(&metrics::BLOCK_ON_TASKS_COUNT, &[name]); - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); let handle = self.handle()?; let exit = self.exit(); - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Starting block_on task"; - "name" => name - ); - - #[cfg(feature = "tracing")] debug!(name, "Starting block_on task"); handle.block_on(async { let output = tokio::select! { output = future => { - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Completed block_on task"; - "name" => name - ); - #[cfg(feature = "tracing")] debug!( name, "Completed block_on task" ); Some(output) - }, + } _ = exit => { - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Cancelled block_on task"; - "name" => name, - ); - #[cfg(feature = "tracing")] debug!( name, "Cancelled block_on task" @@ -406,6 +346,7 @@ impl TaskExecutor { } /// Returns a `Handle` to the current runtime. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn handle(&self) -> Option { self.handle_provider.handle() } @@ -420,13 +361,8 @@ impl TaskExecutor { } /// Get a channel to request shutting down. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn shutdown_sender(&self) -> Sender { self.signal_tx.clone() } - - /// Returns a reference to the logger. - #[cfg(not(feature = "tracing"))] - pub fn log(&self) -> &slog::Logger { - &self.log - } } diff --git a/common/task_executor/src/test_utils.rs b/common/task_executor/src/test_utils.rs index 46fbff7eac..698152f6c1 100644 --- a/common/task_executor/src/test_utils.rs +++ b/common/task_executor/src/test_utils.rs @@ -1,6 +1,4 @@ use crate::TaskExecutor; -pub use logging::test_logger; -use slog::Logger; use std::sync::Arc; use tokio::runtime; @@ -16,7 +14,6 @@ pub struct TestRuntime { runtime: Option>, _runtime_shutdown: async_channel::Sender<()>, pub task_executor: TaskExecutor, - pub log: Logger, } impl Default for TestRuntime { @@ -26,7 +23,6 @@ impl Default for TestRuntime { fn default() -> Self { let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let log = test_logger(); let (runtime, handle) = if let Ok(handle) = runtime::Handle::try_current() { (None, handle) @@ -41,13 +37,12 @@ impl Default for TestRuntime { (Some(runtime), handle) }; - let task_executor = TaskExecutor::new(handle, exit, log.clone(), shutdown_tx); + let task_executor = TaskExecutor::new(handle, exit, shutdown_tx, "test".to_string()); Self { runtime, _runtime_shutdown: runtime_shutdown, task_executor, - log, } } } @@ -59,10 +54,3 @@ impl Drop for TestRuntime { } } } - -impl TestRuntime { - pub fn set_logger(&mut self, log: Logger) { - self.log = log.clone(); - self.task_executor.log = log; - } -} diff --git a/common/warp_utils/Cargo.toml b/common/warp_utils/Cargo.toml index ec2d23686b..32a540a69d 100644 --- a/common/warp_utils/Cargo.toml +++ b/common/warp_utils/Cargo.toml @@ -9,7 +9,6 @@ edition = { workspace = true } bytes = { workspace = true } eth2 = { workspace = true } headers = "0.3.2" -metrics = { workspace = true } safe_arith = { workspace = true } serde = { workspace = true } serde_array_query = "0.1.0" diff --git a/consensus/fork_choice/Cargo.toml b/consensus/fork_choice/Cargo.toml index 3bd18e922a..5c009a5e78 100644 --- a/consensus/fork_choice/Cargo.toml +++ b/consensus/fork_choice/Cargo.toml @@ -8,10 +8,11 @@ edition = { workspace = true } [dependencies] ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } +logging = { workspace = true } metrics = { workspace = true } proto_array = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 4c25be950b..91b44c7af1 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -1,10 +1,10 @@ use crate::metrics::{self, scrape_for_metrics}; use crate::{ForkChoiceStore, InvalidationOperation}; +use logging::crit; use proto_array::{ Block as ProtoBlock, DisallowedReOrgOffsets, ExecutionStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold, }; -use slog::{crit, debug, warn, Logger}; use ssz_derive::{Decode, Encode}; use state_processing::{ per_block_processing::errors::AttesterSlashingValidationError, per_epoch_processing, @@ -13,6 +13,7 @@ use std::cmp::Ordering; use std::collections::BTreeSet; use std::marker::PhantomData; use std::time::Duration; +use tracing::{debug, warn}; use types::{ consts::bellatrix::INTERVALS_PER_SLOT, AbstractExecPayload, AttestationShufflingId, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Checkpoint, @@ -1255,6 +1256,11 @@ where .is_finalized_checkpoint_or_descendant::(block_root) } + pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool { + self.proto_array + .is_descendant(ancestor_root, descendant_root) + } + /// Returns `Ok(true)` if `block_root` has been imported optimistically or deemed invalid. /// /// Returns `Ok(false)` if `block_root`'s execution payload has been elected as fully VALID, if @@ -1365,17 +1371,14 @@ where persisted: &PersistedForkChoice, reset_payload_statuses: ResetPayloadStatuses, spec: &ChainSpec, - log: &Logger, ) -> Result> { let mut proto_array = ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes) .map_err(Error::InvalidProtoArrayBytes)?; let contains_invalid_payloads = proto_array.contains_invalid_payloads(); debug!( - log, - "Restoring fork choice from persisted"; - "reset_payload_statuses" => ?reset_payload_statuses, - "contains_invalid_payloads" => contains_invalid_payloads, + ?reset_payload_statuses, + contains_invalid_payloads, "Restoring fork choice from persisted" ); // Exit early if there are no "invalid" payloads, if requested. @@ -1394,18 +1397,14 @@ where // back to a proto-array which does not have the reset applied. This indicates a // significant error in Lighthouse and warrants detailed investigation. crit!( - log, - "Failed to reset payload statuses"; - "error" => e, - "info" => "please report this error", + error = ?e, + info = "please report this error", + "Failed to reset payload statuses" ); ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes) .map_err(Error::InvalidProtoArrayBytes) } else { - debug!( - log, - "Successfully reset all payload statuses"; - ); + debug!("Successfully reset all payload statuses"); Ok(proto_array) } } @@ -1417,10 +1416,9 @@ where reset_payload_statuses: ResetPayloadStatuses, fc_store: T, spec: &ChainSpec, - log: &Logger, ) -> Result> { let proto_array = - Self::proto_array_from_persisted(&persisted, reset_payload_statuses, spec, log)?; + Self::proto_array_from_persisted(&persisted, reset_payload_statuses, spec)?; let current_slot = fc_store.get_current_slot(); @@ -1444,10 +1442,9 @@ where // an optimistic status so that we can have a head to start from. if let Err(e) = fork_choice.get_head(current_slot, spec) { warn!( - log, - "Could not find head on persisted FC"; - "info" => "resetting all payload statuses and retrying", - "error" => ?e + info = "resetting all payload statuses and retrying", + error = ?e, + "Could not find head on persisted FC" ); // Although we may have already made this call whilst loading `proto_array`, try it // again since we may have mutated the `proto_array` during `get_head` and therefore may diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index b224cde048..95bdee574d 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -25,6 +25,9 @@ pub type E = MainnetEthSpec; pub const VALIDATOR_COUNT: usize = 64; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + /// Defines some delay between when an attestation is created and when it is mutated. pub enum MutationDelay { /// No delay between creation and mutation. @@ -373,7 +376,7 @@ impl ForkChoiceTest { let state = harness .chain .store - .get_state(&state_root, None) + .get_state(&state_root, None, CACHE_STATE_IN_TESTS) .unwrap() .unwrap(); let balances = state diff --git a/consensus/merkle_proof/src/lib.rs b/consensus/merkle_proof/src/lib.rs index b01f3f4429..271e676df1 100644 --- a/consensus/merkle_proof/src/lib.rs +++ b/consensus/merkle_proof/src/lib.rs @@ -34,6 +34,8 @@ pub enum MerkleTree { pub enum MerkleTreeError { // Trying to push in a leaf LeafReached, + // Trying to generate a proof for a non-leaf node + NonLeafProof, // No more space in the MerkleTree MerkleTreeFull, // MerkleTree is invalid @@ -313,8 +315,17 @@ impl MerkleTree { current_depth -= 1; } - debug_assert_eq!(proof.len(), depth); - debug_assert!(current_node.is_leaf()); + if proof.len() != depth { + // This should be unreachable regardless of how the method is called, because we push + // one proof element for each layer of `depth`. + return Err(MerkleTreeError::PleaseNotifyTheDevs); + } + + // Generating a proof for a non-leaf node is invalid and indicates an error on the part of + // the caller. + if !current_node.is_leaf() { + return Err(MerkleTreeError::NonLeafProof); + } // Put proof in bottom-up order. proof.reverse(); diff --git a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs index 24cb51d755..8d4a544196 100644 --- a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs +++ b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs @@ -293,7 +293,6 @@ where )?); Ok(()) }) - .map_err(Error::into) } /// Includes all signatures in `self.block.body.voluntary_exits` for verification. diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index fdeec6f08c..ff7c0204e2 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -60,6 +60,7 @@ pub enum BlockProcessingError { SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), SszDecodeError(DecodeError), + BitfieldError(ssz::BitfieldError), MerkleTreeError(MerkleTreeError), ArithError(ArithError), InconsistentBlockFork(InconsistentFork), @@ -153,6 +154,7 @@ impl From> for BlockProcessingError { BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockProcessingError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e), } @@ -181,6 +183,7 @@ macro_rules! impl_into_block_processing_error_with_index { BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockProcessingError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e), } @@ -215,6 +218,7 @@ pub enum BlockOperationError { BeaconStateError(BeaconStateError), SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ConsensusContext(ContextError), ArithError(ArithError), } @@ -242,6 +246,12 @@ impl From for BlockOperationError { } } +impl From for BlockOperationError { + fn from(error: ssz::BitfieldError) -> Self { + BlockOperationError::BitfieldError(error) + } +} + impl From for BlockOperationError { fn from(e: ArithError) -> Self { BlockOperationError::ArithError(e) @@ -367,6 +377,7 @@ impl From> BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockOperationError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e), } diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index c59449634a..34e9ff120d 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -22,6 +22,9 @@ pub const VALIDATOR_COUNT: usize = 64; pub const EPOCH_OFFSET: u64 = 4; pub const NUM_ATTESTATIONS: u64 = 1; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + /// A cached set of keys. static KEYPAIRS: LazyLock> = LazyLock::new(|| generate_deterministic_keypairs(MAX_VALIDATOR_COUNT)); @@ -1114,9 +1117,10 @@ async fn block_replayer_peeking_state_roots() { .get_blinded_block(&parent_block_root) .unwrap() .unwrap(); + // Cache the state to make CI go brr. let parent_state = harness .chain - .get_state(&parent_block.state_root(), Some(parent_block.slot())) + .get_state(&parent_block.state_root(), Some(parent_block.slot()), true) .unwrap() .unwrap(); diff --git a/consensus/state_processing/src/per_epoch_processing/errors.rs b/consensus/state_processing/src/per_epoch_processing/errors.rs index f45c55a7ac..7485e365ec 100644 --- a/consensus/state_processing/src/per_epoch_processing/errors.rs +++ b/consensus/state_processing/src/per_epoch_processing/errors.rs @@ -19,9 +19,10 @@ pub enum EpochProcessingError { BeaconStateError(BeaconStateError), InclusionError(InclusionError), SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ArithError(safe_arith::ArithError), InconsistentStateFork(InconsistentFork), - InvalidJustificationBit(ssz_types::Error), + InvalidJustificationBit(ssz::BitfieldError), InvalidFlagIndex(usize), MilhouseError(milhouse::Error), EpochCache(EpochCacheError), @@ -49,6 +50,12 @@ impl From for EpochProcessingError { } } +impl From for EpochProcessingError { + fn from(e: ssz::BitfieldError) -> EpochProcessingError { + EpochProcessingError::BitfieldError(e) + } +} + impl From for EpochProcessingError { fn from(e: safe_arith::ArithError) -> EpochProcessingError { EpochProcessingError::ArithError(e) diff --git a/consensus/state_processing/src/per_epoch_processing/single_pass.rs b/consensus/state_processing/src/per_epoch_processing/single_pass.rs index 5c31669a60..af6a0936e2 100644 --- a/consensus/state_processing/src/per_epoch_processing/single_pass.rs +++ b/consensus/state_processing/src/per_epoch_processing/single_pass.rs @@ -175,6 +175,7 @@ pub fn process_epoch_single_pass( let mut earliest_exit_epoch = state.earliest_exit_epoch().ok(); let mut exit_balance_to_consume = state.exit_balance_to_consume().ok(); + let validators_in_consolidations = get_validators_in_consolidations(state); // Split the state into several disjoint mutable borrows. let ( @@ -317,17 +318,26 @@ pub fn process_epoch_single_pass( // `process_effective_balance_updates` if conf.effective_balance_updates { - process_single_effective_balance_update( - validator_info.index, - *balance, - &mut validator, - validator_info.current_epoch_participation, - &mut next_epoch_cache, - progressive_balances, - effective_balances_ctxt, - state_ctxt, - spec, - )?; + if validators_in_consolidations.contains(&validator_info.index) { + process_single_dummy_effective_balance_update( + validator_info.index, + &validator, + &mut next_epoch_cache, + state_ctxt, + )?; + } else { + process_single_effective_balance_update( + validator_info.index, + *balance, + &mut validator, + validator_info.current_epoch_participation, + &mut next_epoch_cache, + progressive_balances, + effective_balances_ctxt, + state_ctxt, + spec, + )?; + } } } @@ -430,6 +440,7 @@ pub fn process_epoch_single_pass( if fork_name.electra_enabled() && conf.pending_consolidations { process_pending_consolidations( state, + &validators_in_consolidations, &mut next_epoch_cache, effective_balances_ctxt, conf.effective_balance_updates, @@ -1026,12 +1037,38 @@ fn process_pending_deposits_for_validator( Ok(()) } +/// Return the set of validators referenced by consolidations, either as source or target. +/// +/// This function is blind to whether the consolidations are valid and capable of being processed, +/// it just returns the set of all indices present in consolidations. This is *sufficient* to +/// make consolidations play nicely with effective balance updates. The algorithm used is: +/// +/// - In the single pass: apply effective balance updates for all validators *not* referenced by +/// consolidations. +/// - Apply consolidations. +/// - Apply effective balance updates for all validators previously skipped. +/// +/// Prior to Electra, the empty set is returned. +fn get_validators_in_consolidations(state: &BeaconState) -> BTreeSet { + let mut referenced_validators = BTreeSet::new(); + + if let Ok(pending_consolidations) = state.pending_consolidations() { + for pending_consolidation in pending_consolidations { + referenced_validators.insert(pending_consolidation.source_index as usize); + referenced_validators.insert(pending_consolidation.target_index as usize); + } + } + + referenced_validators +} + /// We process pending consolidations after all of single-pass epoch processing, and then patch up /// the effective balances for affected validators. /// /// This is safe because processing consolidations does not depend on the `effective_balance`. fn process_pending_consolidations( state: &mut BeaconState, + validators_in_consolidations: &BTreeSet, next_epoch_cache: &mut PreEpochCache, effective_balances_ctxt: &EffectiveBalancesContext, perform_effective_balance_updates: bool, @@ -1042,8 +1079,6 @@ fn process_pending_consolidations( let next_epoch = state.next_epoch()?; let pending_consolidations = state.pending_consolidations()?.clone(); - let mut affected_validators = BTreeSet::new(); - for pending_consolidation in &pending_consolidations { let source_index = pending_consolidation.source_index as usize; let target_index = pending_consolidation.target_index as usize; @@ -1069,9 +1104,6 @@ fn process_pending_consolidations( decrease_balance(state, source_index, source_effective_balance)?; increase_balance(state, target_index, source_effective_balance)?; - affected_validators.insert(source_index); - affected_validators.insert(target_index); - next_pending_consolidation.safe_add_assign(1)?; } @@ -1087,7 +1119,7 @@ fn process_pending_consolidations( // Re-process effective balance updates for validators affected by consolidations. let (validators, balances, _, current_epoch_participation, _, progressive_balances, _, _) = state.mutable_validator_fields()?; - for validator_index in affected_validators { + for &validator_index in validators_in_consolidations { let balance = *balances .get(validator_index) .ok_or(BeaconStateError::UnknownValidator(validator_index))?; @@ -1129,6 +1161,28 @@ impl EffectiveBalancesContext { } } +/// This function is called for validators that do not have their effective balance updated as +/// part of the single-pass loop. For these validators we compute their true effective balance +/// update after processing consolidations. However, to maintain the invariants of the +/// `PreEpochCache` we must register _some_ effective balance for them immediately. +fn process_single_dummy_effective_balance_update( + validator_index: usize, + validator: &Cow, + next_epoch_cache: &mut PreEpochCache, + state_ctxt: &StateContext, +) -> Result<(), Error> { + // Populate the effective balance cache with the current effective balance. This will be + // overriden when `process_single_effective_balance_update` is called. + let is_active_next_epoch = validator.is_active_at(state_ctxt.next_epoch); + let temporary_effective_balance = validator.effective_balance; + next_epoch_cache.update_effective_balance( + validator_index, + temporary_effective_balance, + is_active_next_epoch, + )?; + Ok(()) +} + /// This function abstracts over phase0 and Electra effective balance processing. #[allow(clippy::too_many_arguments)] fn process_single_effective_balance_update( diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index 79beb81282..013230f158 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -28,7 +28,6 @@ hex = { workspace = true } int_to_bytes = { workspace = true } itertools = { workspace = true } kzg = { workspace = true } -log = { workspace = true } maplit = { workspace = true } merkle_proof = { workspace = true } metastruct = "0.1.0" @@ -39,18 +38,18 @@ rand_xorshift = "0.3.0" rayon = { workspace = true } regex = { workspace = true } rpds = { workspace = true } -rusqlite = { workspace = true } +rusqlite = { workspace = true, optional = true } safe_arith = { workspace = true } serde = { workspace = true, features = ["rc"] } serde_json = { workspace = true } serde_yaml = { workspace = true } -slog = { workspace = true } smallvec = { workspace = true } ssz_types = { workspace = true, features = ["arbitrary"] } superstruct = { workspace = true } swap_or_not_shuffle = { workspace = true, features = ["arbitrary"] } tempfile = { workspace = true } test_random_derive = { path = "../../common/test_random_derive" } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } @@ -65,7 +64,7 @@ tokio = { workspace = true } default = ["sqlite", "legacy-arith"] # Allow saturating arithmetic on slots and epochs. Enabled by default, but deprecated. legacy-arith = [] -sqlite = [] +sqlite = ["dep:rusqlite"] # The `arbitrary-fuzz` feature is a no-op provided for backwards compatibility. # For simplicity `Arbitrary` is now derived regardless of the feature's presence. arbitrary-fuzz = [] diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index 1485842edb..5d147f1e86 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -18,6 +18,7 @@ use super::{ #[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), AlreadySigned(usize), IncorrectStateVariant, InvalidCommitteeLength, @@ -223,7 +224,7 @@ impl Attestation { } } - pub fn get_aggregation_bit(&self, index: usize) -> Result { + pub fn get_aggregation_bit(&self, index: usize) -> Result { match self { Attestation::Base(att) => att.aggregation_bits.get(index), Attestation::Electra(att) => att.aggregation_bits.get(index), @@ -353,13 +354,13 @@ impl AttestationElectra { if self .aggregation_bits .get(committee_position) - .map_err(Error::SszTypesError)? + .map_err(Error::BitfieldError)? { Err(Error::AlreadySigned(committee_position)) } else { self.aggregation_bits .set(committee_position, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; self.signature.add_assign(signature); @@ -427,13 +428,13 @@ impl AttestationBase { if self .aggregation_bits .get(committee_position) - .map_err(Error::SszTypesError)? + .map_err(Error::BitfieldError)? { Err(Error::AlreadySigned(committee_position)) } else { self.aggregation_bits .set(committee_position, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; self.signature.add_assign(signature); @@ -443,7 +444,7 @@ impl AttestationBase { pub fn extend_aggregation_bits( &self, - ) -> Result, ssz_types::Error> { + ) -> Result, ssz::BitfieldError> { self.aggregation_bits.resize::() } } @@ -600,12 +601,12 @@ mod tests { let attestation_data = size_of::(); let signature = size_of::(); - assert_eq!(aggregation_bits, 56); + assert_eq!(aggregation_bits, 152); assert_eq!(attestation_data, 128); assert_eq!(signature, 288 + 16); let attestation_expected = aggregation_bits + attestation_data + signature; - assert_eq!(attestation_expected, 488); + assert_eq!(attestation_expected, 584); assert_eq!( size_of::>(), attestation_expected @@ -623,13 +624,13 @@ mod tests { size_of::::MaxCommitteesPerSlot>>(); let signature = size_of::(); - assert_eq!(aggregation_bits, 56); - assert_eq!(committee_bits, 56); + assert_eq!(aggregation_bits, 152); + assert_eq!(committee_bits, 152); assert_eq!(attestation_data, 128); assert_eq!(signature, 288 + 16); let attestation_expected = aggregation_bits + committee_bits + attestation_data + signature; - assert_eq!(attestation_expected, 544); + assert_eq!(attestation_expected, 736); assert_eq!( size_of::>(), attestation_expected diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 3f75790a35..10c1a11ede 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -277,9 +277,9 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, // https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody generalized_index .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - .ok_or(Error::IndexNotSupported(generalized_index))? + .ok_or(Error::GeneralizedIndexNotSupported(generalized_index))? } - _ => return Err(Error::IndexNotSupported(generalized_index)), + _ => return Err(Error::GeneralizedIndexNotSupported(generalized_index)), }; let leaves = self.body_merkle_leaves(); @@ -971,6 +971,7 @@ impl From>> Option>, ) { + #[allow(clippy::useless_conversion)] // Not a useless conversion fn from(body: BeaconBlockBody>) -> Self { map_beacon_block_body!(body, |inner, cons| { let (block, payload) = inner.into(); diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 157271b227..4aed79898d 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -157,6 +157,7 @@ pub enum Error { current_fork: ForkName, }, TotalActiveBalanceDiffUninitialized, + GeneralizedIndexNotSupported(usize), IndexNotSupported(usize), InvalidFlagIndex(usize), MerkleTreeError(merkle_proof::MerkleTreeError), @@ -2580,11 +2581,12 @@ impl BeaconState { // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate - let field_index = if self.fork_name_unchecked().electra_enabled() { + let field_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA } else { light_client_update::CURRENT_SYNC_COMMITTEE_INDEX }; + let field_index = field_gindex.safe_sub(self.num_fields_pow2())?; let leaves = self.get_beacon_state_leaves(); self.generate_proof(field_index, &leaves) } @@ -2594,11 +2596,12 @@ impl BeaconState { // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate - let field_index = if self.fork_name_unchecked().electra_enabled() { + let field_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::NEXT_SYNC_COMMITTEE_INDEX_ELECTRA } else { light_client_update::NEXT_SYNC_COMMITTEE_INDEX }; + let field_index = field_gindex.safe_sub(self.num_fields_pow2())?; let leaves = self.get_beacon_state_leaves(); self.generate_proof(field_index, &leaves) } @@ -2606,17 +2609,24 @@ impl BeaconState { pub fn compute_finalized_root_proof(&self) -> Result, Error> { // Finalized root is the right child of `finalized_checkpoint`, divide by two to get // the generalized index of `state.finalized_checkpoint`. - let field_index = if self.fork_name_unchecked().electra_enabled() { - // Index should be 169/2 - 64 = 20 which matches the position - // of `finalized_checkpoint` in `BeaconState` + let checkpoint_root_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::FINALIZED_ROOT_INDEX_ELECTRA } else { - // Index should be 105/2 - 32 = 20 which matches the position - // of `finalized_checkpoint` in `BeaconState` light_client_update::FINALIZED_ROOT_INDEX }; + let checkpoint_gindex = checkpoint_root_gindex / 2; + + // Convert gindex to index by subtracting 2**depth (gindex = 2**depth + index). + // + // After Electra, the index should be 169/2 - 64 = 20 which matches the position + // of `finalized_checkpoint` in `BeaconState`. + // + // Prior to Electra, the index should be 105/2 - 32 = 20 which matches the position + // of `finalized_checkpoint` in `BeaconState`. + let checkpoint_index = checkpoint_gindex.safe_sub(self.num_fields_pow2())?; + let leaves = self.get_beacon_state_leaves(); - let mut proof = self.generate_proof(field_index, &leaves)?; + let mut proof = self.generate_proof(checkpoint_index, &leaves)?; proof.insert(0, self.finalized_checkpoint().epoch.tree_hash_root()); Ok(proof) } @@ -2626,6 +2636,10 @@ impl BeaconState { field_index: usize, leaves: &[Hash256], ) -> Result, Error> { + if field_index >= leaves.len() { + return Err(Error::IndexNotSupported(field_index)); + } + let depth = self.num_fields_pow2().ilog2() as usize; let tree = merkle_proof::MerkleTree::create(leaves, depth); let (_, proof) = tree.generate_proof(field_index, depth)?; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 0dd91edc3c..e1fce47975 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -213,12 +213,16 @@ impl LightClientUpdate { .map_err(|_| Error::InconsistentFork)? { ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Altair | ForkName::Bellatrix => { + fork_name @ ForkName::Altair | fork_name @ ForkName::Bellatrix => { let attested_header = LightClientHeaderAltair::block_to_light_client_header(attested_block)?; let finalized_header = if let Some(finalized_block) = finalized_block { - LightClientHeaderAltair::block_to_light_client_header(finalized_block)? + if finalized_block.fork_name_unchecked() == fork_name { + LightClientHeaderAltair::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderAltair::default() + } } else { LightClientHeaderAltair::default() }; @@ -233,12 +237,16 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - ForkName::Capella => { + fork_name @ ForkName::Capella => { let attested_header = LightClientHeaderCapella::block_to_light_client_header(attested_block)?; let finalized_header = if let Some(finalized_block) = finalized_block { - LightClientHeaderCapella::block_to_light_client_header(finalized_block)? + if finalized_block.fork_name_unchecked() == fork_name { + LightClientHeaderCapella::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderCapella::default() + } } else { LightClientHeaderCapella::default() }; @@ -253,12 +261,16 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - ForkName::Deneb => { + fork_name @ ForkName::Deneb => { let attested_header = LightClientHeaderDeneb::block_to_light_client_header(attested_block)?; let finalized_header = if let Some(finalized_block) = finalized_block { - LightClientHeaderDeneb::block_to_light_client_header(finalized_block)? + if finalized_block.fork_name_unchecked() == fork_name { + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderDeneb::default() + } } else { LightClientHeaderDeneb::default() }; @@ -273,12 +285,16 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - ForkName::Electra => { + fork_name @ ForkName::Electra => { let attested_header = LightClientHeaderElectra::block_to_light_client_header(attested_block)?; let finalized_header = if let Some(finalized_block) = finalized_block { - LightClientHeaderElectra::block_to_light_client_header(finalized_block)? + if finalized_block.fork_name_unchecked() == fork_name { + LightClientHeaderElectra::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderElectra::default() + } } else { LightClientHeaderElectra::default() }; @@ -293,12 +309,16 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - ForkName::Fulu => { + fork_name @ ForkName::Fulu => { let attested_header = LightClientHeaderFulu::block_to_light_client_header(attested_block)?; let finalized_header = if let Some(finalized_block) = finalized_block { - LightClientHeaderFulu::block_to_light_client_header(finalized_block)? + if finalized_block.fork_name_unchecked() == fork_name { + LightClientHeaderFulu::block_to_light_client_header(finalized_block)? + } else { + LightClientHeaderFulu::default() + } } else { LightClientHeaderFulu::default() }; diff --git a/consensus/types/src/runtime_var_list.rs b/consensus/types/src/runtime_var_list.rs index 857073b3b8..d6b1c10e99 100644 --- a/consensus/types/src/runtime_var_list.rs +++ b/consensus/types/src/runtime_var_list.rs @@ -134,13 +134,13 @@ impl RuntimeVariableList { ))); } - bytes - .chunks(::ssz_fixed_len()) - .try_fold(Vec::with_capacity(num_items), |mut vec, chunk| { + bytes.chunks(::ssz_fixed_len()).try_fold( + Vec::with_capacity(num_items), + |mut vec, chunk| { vec.push(::from_ssz_bytes(chunk)?); Ok(vec) - }) - .map(Into::into)? + }, + )? } else { ssz::decode_list_of_variable_length_items(bytes, Some(max_len))? }; diff --git a/consensus/types/src/slot_epoch_macros.rs b/consensus/types/src/slot_epoch_macros.rs index 42e7a0f2ee..eee267355a 100644 --- a/consensus/types/src/slot_epoch_macros.rs +++ b/consensus/types/src/slot_epoch_macros.rs @@ -227,17 +227,6 @@ macro_rules! impl_display { write!(f, "{}", self.0) } } - - impl slog::Value for $type { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::Value::serialize(&self.0, record, key, serializer) - } - } }; } diff --git a/consensus/types/src/sync_aggregate.rs b/consensus/types/src/sync_aggregate.rs index 43f72a3924..12b91501ae 100644 --- a/consensus/types/src/sync_aggregate.rs +++ b/consensus/types/src/sync_aggregate.rs @@ -11,6 +11,7 @@ use tree_hash_derive::TreeHash; #[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ArithError(ArithError), } @@ -68,7 +69,7 @@ impl SyncAggregate { sync_aggregate .sync_committee_bits .set(participant_index, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; } } sync_aggregate diff --git a/consensus/types/src/sync_committee_contribution.rs b/consensus/types/src/sync_committee_contribution.rs index 9bae770fe5..58983d26ec 100644 --- a/consensus/types/src/sync_committee_contribution.rs +++ b/consensus/types/src/sync_committee_contribution.rs @@ -9,6 +9,7 @@ use tree_hash_derive::TreeHash; #[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), AlreadySigned(usize), } @@ -51,7 +52,7 @@ impl SyncCommitteeContribution { ) -> Result { let mut bits = BitVector::new(); bits.set(validator_sync_committee_index, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; Ok(Self { slot: message.slot, beacon_block_root: message.beacon_block_root, diff --git a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs index 92534369ee..f30afda257 100644 --- a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs +++ b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs @@ -1,8 +1,8 @@ use crate::*; use eth2_interop_keypairs::{keypair, keypairs_from_yaml_file}; -use log::debug; use rayon::prelude::*; use std::path::PathBuf; +use tracing::debug; /// Generates `validator_count` keypairs where the secret key is derived solely from the index of /// the validator. diff --git a/consensus/types/src/validator_registration_data.rs b/consensus/types/src/validator_registration_data.rs index cdafd355e7..345771074c 100644 --- a/consensus/types/src/validator_registration_data.rs +++ b/consensus/types/src/validator_registration_data.rs @@ -4,7 +4,7 @@ use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; /// Validator registration, for use in interacting with servers implementing the builder API. -#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)] pub struct SignedValidatorRegistrationData { pub message: ValidatorRegistrationData, pub signature: Signature, diff --git a/crypto/bls/src/generic_secret_key.rs b/crypto/bls/src/generic_secret_key.rs index a0a4331110..62bfc1467d 100644 --- a/crypto/bls/src/generic_secret_key.rs +++ b/crypto/bls/src/generic_secret_key.rs @@ -61,6 +61,11 @@ where GenericPublicKey::from_point(self.point.public_key()) } + /// Returns a reference to the underlying BLS point. + pub fn point(&self) -> &Sec { + &self.point + } + /// Serialize `self` as compressed bytes. /// /// ## Note @@ -89,3 +94,20 @@ where } } } + +impl GenericSecretKey +where + Sig: TSignature, + Pub: TPublicKey, + Sec: TSecretKey + Clone, +{ + /// Instantiates `Self` from a `point`. + /// Takes a reference, as moves might accidentally leave behind key material + pub fn from_point(point: &Sec) -> Self { + Self { + point: point.clone(), + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + } + } +} diff --git a/crypto/bls/src/generic_signature.rs b/crypto/bls/src/generic_signature.rs index 05e0a222bd..0b375d3edd 100644 --- a/crypto/bls/src/generic_signature.rs +++ b/crypto/bls/src/generic_signature.rs @@ -14,6 +14,9 @@ use tree_hash::TreeHash; /// The byte-length of a BLS signature when serialized in compressed form. pub const SIGNATURE_BYTES_LEN: usize = 96; +/// The byte-length of a BLS signature when serialized in uncompressed form. +pub const SIGNATURE_UNCOMPRESSED_BYTES_LEN: usize = 192; + /// Represents the signature at infinity. pub const INFINITY_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [ 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -22,6 +25,16 @@ pub const INFINITY_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [ 0, ]; +pub const INFINITY_SIGNATURE_UNCOMPRESSED: [u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN] = [ + 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + /// The compressed bytes used to represent `GenericSignature::empty()`. pub const NONE_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [0; SIGNATURE_BYTES_LEN]; @@ -31,9 +44,15 @@ pub trait TSignature: Sized + Clone { /// Serialize `self` as compressed bytes. fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN]; + /// Serialize `self` as uncompressed bytes. + fn serialize_uncompressed(&self) -> [u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN]; + /// Deserialize `self` from compressed bytes. fn deserialize(bytes: &[u8]) -> Result; + /// Serialize `self` from uncompressed bytes. + fn deserialize_uncompressed(bytes: &[u8]) -> Result; + /// Returns `true` if `self` is a signature across `msg` by `pubkey`. fn verify(&self, pubkey: &GenericPublicKey, msg: Hash256) -> bool; } @@ -93,12 +112,12 @@ where } /// Returns a reference to the underlying BLS point. - pub(crate) fn point(&self) -> Option<&Sig> { + pub fn point(&self) -> Option<&Sig> { self.point.as_ref() } /// Instantiates `Self` from a `point`. - pub(crate) fn from_point(point: Sig, is_infinity: bool) -> Self { + pub fn from_point(point: Sig, is_infinity: bool) -> Self { Self { point: Some(point), is_infinity, @@ -115,6 +134,13 @@ where } } + /// Serialize `self` as compressed bytes. + pub fn serialize_uncompressed(&self) -> Option<[u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN]> { + self.point + .as_ref() + .map(|point| point.serialize_uncompressed()) + } + /// Deserialize `self` from compressed bytes. pub fn deserialize(bytes: &[u8]) -> Result { let point = if bytes == &NONE_SIGNATURE[..] { @@ -129,6 +155,17 @@ where _phantom: PhantomData, }) } + + /// Deserialize `self` from uncompressed bytes. + pub fn deserialize_uncompressed(bytes: &[u8]) -> Result { + // The "none signature" is a beacon chain concept. As we never directly deal with + // uncompressed signatures on the beacon chain, it does not apply here. + Ok(Self { + point: Some(Sig::deserialize_uncompressed(bytes)?), + is_infinity: bytes == &INFINITY_SIGNATURE_UNCOMPRESSED[..], + _phantom: PhantomData, + }) + } } impl GenericSignature diff --git a/crypto/bls/src/impls/blst.rs b/crypto/bls/src/impls/blst.rs index baa704e05a..6ca0fe09b2 100644 --- a/crypto/bls/src/impls/blst.rs +++ b/crypto/bls/src/impls/blst.rs @@ -5,7 +5,7 @@ use crate::{ GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN, PUBLIC_KEY_UNCOMPRESSED_BYTES_LEN, }, generic_secret_key::TSecretKey, - generic_signature::{TSignature, SIGNATURE_BYTES_LEN}, + generic_signature::{TSignature, SIGNATURE_BYTES_LEN, SIGNATURE_UNCOMPRESSED_BYTES_LEN}, BlstError, Error, Hash256, ZeroizeHash, INFINITY_SIGNATURE, }; pub use blst::min_pk as blst_core; @@ -189,10 +189,18 @@ impl TSignature for blst_core::Signature { self.to_bytes() } + fn serialize_uncompressed(&self) -> [u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN] { + self.serialize() + } + fn deserialize(bytes: &[u8]) -> Result { Self::from_bytes(bytes).map_err(Into::into) } + fn deserialize_uncompressed(bytes: &[u8]) -> Result { + Self::deserialize(bytes).map_err(Into::into) + } + fn verify(&self, pubkey: &blst_core::PublicKey, msg: Hash256) -> bool { // Public keys have already been checked for subgroup and infinity // Check Signature inside function for subgroup diff --git a/crypto/bls/src/impls/fake_crypto.rs b/crypto/bls/src/impls/fake_crypto.rs index a09fb347e6..7273697597 100644 --- a/crypto/bls/src/impls/fake_crypto.rs +++ b/crypto/bls/src/impls/fake_crypto.rs @@ -5,7 +5,7 @@ use crate::{ GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN, PUBLIC_KEY_UNCOMPRESSED_BYTES_LEN, }, generic_secret_key::{TSecretKey, SECRET_KEY_BYTES_LEN}, - generic_signature::{TSignature, SIGNATURE_BYTES_LEN}, + generic_signature::{TSignature, SIGNATURE_BYTES_LEN, SIGNATURE_UNCOMPRESSED_BYTES_LEN}, Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE, }; @@ -106,12 +106,22 @@ impl TSignature for Signature { self.0 } + fn serialize_uncompressed(&self) -> [u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN] { + let mut ret = [0; SIGNATURE_UNCOMPRESSED_BYTES_LEN]; + ret[0..SIGNATURE_BYTES_LEN].copy_from_slice(&self.0); + ret + } + fn deserialize(bytes: &[u8]) -> Result { let mut signature = Self::infinity(); signature.0[..].copy_from_slice(&bytes[0..SIGNATURE_BYTES_LEN]); Ok(signature) } + fn deserialize_uncompressed(bytes: &[u8]) -> Result { + Self::deserialize(bytes) + } + fn verify(&self, _pubkey: &PublicKey, _msg: Hash256) -> bool { true } diff --git a/crypto/bls/src/lib.rs b/crypto/bls/src/lib.rs index 6ea85548c0..13b6dc2f2c 100644 --- a/crypto/bls/src/lib.rs +++ b/crypto/bls/src/lib.rs @@ -37,7 +37,10 @@ pub use generic_public_key::{ INFINITY_PUBLIC_KEY, PUBLIC_KEY_BYTES_LEN, PUBLIC_KEY_UNCOMPRESSED_BYTES_LEN, }; pub use generic_secret_key::SECRET_KEY_BYTES_LEN; -pub use generic_signature::{INFINITY_SIGNATURE, SIGNATURE_BYTES_LEN}; +pub use generic_signature::{ + INFINITY_SIGNATURE, INFINITY_SIGNATURE_UNCOMPRESSED, SIGNATURE_BYTES_LEN, + SIGNATURE_UNCOMPRESSED_BYTES_LEN, +}; pub use get_withdrawal_credentials::get_withdrawal_credentials; pub use zeroize_hash::ZeroizeHash; diff --git a/crypto/bls/tests/tests.rs b/crypto/bls/tests/tests.rs index 26215771b5..611dabbd64 100644 --- a/crypto/bls/tests/tests.rs +++ b/crypto/bls/tests/tests.rs @@ -1,4 +1,7 @@ -use bls::{FixedBytesExtended, Hash256, INFINITY_SIGNATURE, SECRET_KEY_BYTES_LEN}; +use bls::{ + FixedBytesExtended, Hash256, INFINITY_SIGNATURE, INFINITY_SIGNATURE_UNCOMPRESSED, + SECRET_KEY_BYTES_LEN, +}; use ssz::{Decode, Encode}; use std::borrow::Cow; use std::fmt::Debug; @@ -37,6 +40,18 @@ macro_rules! test_suite { assert!(AggregateSignature::infinity().is_infinity()); } + #[test] + fn infinity_sig_serializations_match() { + let sig = Signature::deserialize(&INFINITY_SIGNATURE).unwrap(); + assert_eq!( + sig.serialize_uncompressed().unwrap(), + INFINITY_SIGNATURE_UNCOMPRESSED + ); + let sig = + Signature::deserialize_uncompressed(&INFINITY_SIGNATURE_UNCOMPRESSED).unwrap(); + assert_eq!(sig.serialize(), INFINITY_SIGNATURE); + } + #[test] fn ssz_round_trip_multiple_types() { let mut agg_sig = AggregateSignature::infinity(); diff --git a/database_manager/Cargo.toml b/database_manager/Cargo.toml index a7a54b1416..99bef75a72 100644 --- a/database_manager/Cargo.toml +++ b/database_manager/Cargo.toml @@ -11,7 +11,7 @@ clap_utils = { workspace = true } environment = { workspace = true } hex = { workspace = true } serde = { workspace = true } -slog = { workspace = true } store = { workspace = true } strum = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index bed90df9df..f38c28d8b0 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -12,7 +12,6 @@ use clap::ValueEnum; use cli::{Compact, Inspect}; use environment::{Environment, RuntimeContext}; use serde::{Deserialize, Serialize}; -use slog::{info, warn, Logger}; use std::fs; use std::io::Write; use std::path::PathBuf; @@ -24,6 +23,7 @@ use store::{ DBColumn, HotColdDB, }; use strum::{EnumString, EnumVariantNames}; +use tracing::{info, warn}; use types::{BeaconState, EthSpec, Slot}; fn parse_client_config( @@ -49,7 +49,6 @@ fn parse_client_config( pub fn display_db_version( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = runtime_context.eth2_config.spec.clone(); let hot_path = client_config.get_db_path(); @@ -67,16 +66,14 @@ pub fn display_db_version( }, client_config.store, spec, - log.clone(), )?; - info!(log, "Database version: {}", version.as_u64()); + info!(version = version.as_u64(), "Database"); if version != CURRENT_SCHEMA_VERSION { info!( - log, - "Latest schema version: {}", - CURRENT_SCHEMA_VERSION.as_u64(), + current_schema_version = CURRENT_SCHEMA_VERSION.as_u64(), + "Latest schema" ); } @@ -260,7 +257,6 @@ fn parse_compact_config(compact_config: &Compact) -> Result( compact_config: CompactConfig, client_config: ClientConfig, - log: Logger, ) -> Result<(), Error> { let hot_path = client_config.get_db_path(); let cold_path = client_config.get_freezer_db_path(); @@ -284,10 +280,9 @@ pub fn compact_db( ) }; info!( - log, - "Compacting database"; - "db" => db_name, - "column" => ?column + db = db_name, + column = ?column, + "Compacting database" ); sub_db.compact_column(column)?; Ok(()) @@ -308,7 +303,6 @@ pub fn migrate_db( client_config: ClientConfig, mut genesis_state: BeaconState, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = runtime_context.eth2_config.spec.clone(); let hot_path = client_config.get_db_path(); @@ -327,14 +321,12 @@ pub fn migrate_db( }, client_config.store.clone(), spec.clone(), - log.clone(), )?; info!( - log, - "Migrating database schema"; - "from" => from.as_u64(), - "to" => to.as_u64(), + from = from.as_u64(), + to = to.as_u64(), + "Migrating database schema" ); let genesis_state_root = genesis_state.canonical_root()?; @@ -343,14 +335,12 @@ pub fn migrate_db( Some(genesis_state_root), from, to, - log, ) } pub fn prune_payloads( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -364,7 +354,6 @@ pub fn prune_payloads( |_, _, _| Ok(()), client_config.store, spec.clone(), - log, )?; // If we're trigging a prune manually then ignore the check on the split's parent that bails @@ -376,7 +365,6 @@ pub fn prune_payloads( pub fn prune_blobs( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -390,7 +378,6 @@ pub fn prune_blobs( |_, _, _| Ok(()), client_config.store, spec.clone(), - log, )?; // If we're triggering a prune manually then ignore the check on `epochs_per_blob_prune` that @@ -413,7 +400,6 @@ pub fn prune_states( prune_config: PruneStatesConfig, mut genesis_state: BeaconState, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), String> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -427,7 +413,6 @@ pub fn prune_states( |_, _, _| Ok(()), client_config.store, spec.clone(), - log.clone(), ) .map_err(|e| format!("Unable to open database: {e:?}"))?; @@ -447,20 +432,14 @@ pub fn prune_states( // Check that the user has confirmed they want to proceed. if !prune_config.confirm { if db.get_anchor_info().full_state_pruning_enabled() { - info!(log, "States have already been pruned"); + info!("States have already been pruned"); return Ok(()); } - info!(log, "Ready to prune states"); - warn!( - log, - "Pruning states is irreversible"; - ); - warn!( - log, - "Re-run this command with --confirm to commit to state deletion" - ); - info!(log, "Nothing has been pruned on this run"); + info!("Ready to prune states"); + warn!("Pruning states is irreversible"); + warn!("Re-run this command with --confirm to commit to state deletion"); + info!("Nothing has been pruned on this run"); return Err("Error: confirmation flag required".into()); } @@ -471,7 +450,7 @@ pub fn prune_states( db.prune_historic_states(genesis_state_root, &genesis_state) .map_err(|e| format!("Failed to prune due to error: {e:?}"))?; - info!(log, "Historic states pruned successfully"); + info!("Historic states pruned successfully"); Ok(()) } @@ -483,7 +462,6 @@ pub fn run( ) -> Result<(), String> { let client_config = parse_client_config(cli_args, db_manager_config, &env)?; let context = env.core_context(); - let log = context.log().clone(); let format_err = |e| format!("Fatal error: {:?}", e); let get_genesis_state = || { @@ -498,7 +476,6 @@ pub fn run( network_config.genesis_state::( client_config.genesis_state_url.as_deref(), client_config.genesis_state_url_timeout, - &log, ), "get_genesis_state", ) @@ -511,30 +488,29 @@ pub fn run( cli::DatabaseManagerSubcommand::Migrate(migrate_config) => { let migrate_config = parse_migrate_config(migrate_config)?; let genesis_state = get_genesis_state()?; - migrate_db(migrate_config, client_config, genesis_state, &context, log) - .map_err(format_err) + migrate_db(migrate_config, client_config, genesis_state, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::Inspect(inspect_config) => { let inspect_config = parse_inspect_config(inspect_config)?; inspect_db::(inspect_config, client_config) } cli::DatabaseManagerSubcommand::Version(_) => { - display_db_version(client_config, &context, log).map_err(format_err) + display_db_version(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PrunePayloads(_) => { - prune_payloads(client_config, &context, log).map_err(format_err) + prune_payloads(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PruneBlobs(_) => { - prune_blobs(client_config, &context, log).map_err(format_err) + prune_blobs(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PruneStates(prune_states_config) => { let prune_config = parse_prune_states_config(prune_states_config)?; let genesis_state = get_genesis_state()?; - prune_states(client_config, prune_config, genesis_state, &context, log) + prune_states(client_config, prune_config, genesis_state, &context) } cli::DatabaseManagerSubcommand::Compact(compact_config) => { let compact_config = parse_compact_config(compact_config)?; - compact_db::(compact_config, client_config, log).map_err(format_err) + compact_db::(compact_config, client_config).map_err(format_err) } } } diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 72be77a70b..22b19f7413 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lcli" description = "Lighthouse CLI (modeled after zcli)" -version = "6.0.1" +version = "7.0.0-beta.5" authors = ["Paul Hauner "] edition = { workspace = true } @@ -34,10 +34,11 @@ rayon = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } -sloggers = { workspace = true } snap = { workspace = true } state_processing = { workspace = true } store = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } validator_dir = { workspace = true } diff --git a/lcli/src/block_root.rs b/lcli/src/block_root.rs index a90a4843d8..80087fd6d4 100644 --- a/lcli/src/block_root.rs +++ b/lcli/src/block_root.rs @@ -32,9 +32,9 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{EthSpec, FullPayload, SignedBeaconBlock}; const HTTP_TIMEOUT: Duration = Duration::from_secs(5); @@ -102,7 +102,7 @@ pub fn run( } if let Some(block_root) = block_root { - info!("Block root is {:?}", block_root); + info!(%block_root,"Block root"); } Ok(()) diff --git a/lcli/src/main.rs b/lcli/src/main.rs index f055a23b36..05f4900c46 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -18,6 +18,7 @@ use parse_ssz::run_parse_ssz; use std::path::PathBuf; use std::process; use std::str::FromStr; +use tracing_subscriber::filter::LevelFilter; use types::{EthSpec, EthSpecId}; fn main() { @@ -552,6 +553,15 @@ fn main() { until Prague is triggered on mainnet.") .display_order(0) ) + .arg( + Arg::new("osaka-time") + .long("osaka-time") + .value_name("UNIX_TIMESTAMP") + .action(ArgAction::Set) + .help("The payload timestamp that enables Osaka. No default is provided \ + until Osaka is triggered on mainnet.") + .display_order(0) + ) ) .subcommand( Command::new("http-sync") @@ -643,24 +653,31 @@ fn main() { } fn run(env_builder: EnvironmentBuilder, matches: &ArgMatches) -> Result<(), String> { + let (env_builder, _file_logging_layer, _stdout_logging_layer, _sse_logging_layer_opt) = + env_builder + .multi_threaded_tokio_runtime() + .map_err(|e| format!("should start tokio runtime: {:?}", e))? + .init_tracing( + LoggerConfig { + path: None, + debug_level: LevelFilter::TRACE, + logfile_debug_level: LevelFilter::TRACE, + log_format: None, + logfile_format: None, + log_color: true, + logfile_color: false, + disable_log_timestamp: false, + max_log_size: 0, + max_log_number: 0, + compression: false, + is_restricted: true, + sse_logging: false, // No SSE Logging in LCLI + extra_info: false, + }, + "", + ); + let env = env_builder - .multi_threaded_tokio_runtime() - .map_err(|e| format!("should start tokio runtime: {:?}", e))? - .initialize_logger(LoggerConfig { - path: None, - debug_level: String::from("trace"), - logfile_debug_level: String::from("trace"), - log_format: None, - logfile_format: None, - log_color: false, - disable_log_timestamp: false, - max_log_size: 0, - max_log_number: 0, - compression: false, - is_restricted: true, - sse_logging: false, // No SSE Logging in LCLI - }) - .map_err(|e| format!("should start logger: {:?}", e))? .build() .map_err(|e| format!("should build env: {:?}", e))?; diff --git a/lcli/src/parse_ssz.rs b/lcli/src/parse_ssz.rs index dd13f6847b..f1e5c5759a 100644 --- a/lcli/src/parse_ssz.rs +++ b/lcli/src/parse_ssz.rs @@ -1,7 +1,6 @@ use clap::ArgMatches; use clap_utils::parse_required; use eth2_network_config::Eth2NetworkConfig; -use log::info; use serde::Serialize; use snap::raw::Decoder; use ssz::Decode; @@ -9,6 +8,7 @@ use std::fs; use std::fs::File; use std::io::Read; use std::str::FromStr; +use tracing::info; use types::*; enum OutputFormat { @@ -59,7 +59,7 @@ pub fn run_parse_ssz( spec.config_name.as_deref().unwrap_or("unknown"), E::spec_name() ); - info!("Type: {type_str}"); + info!(%type_str, "Type"); // More fork-specific decoders may need to be added in future, but shouldn't be 100% necessary, // as the fork-generic decoder will always be available (requires correct --network flag). diff --git a/lcli/src/skip_slots.rs b/lcli/src/skip_slots.rs index 2ad79051ea..834123e939 100644 --- a/lcli/src/skip_slots.rs +++ b/lcli/src/skip_slots.rs @@ -50,7 +50,6 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use ssz::Encode; use state_processing::state_advance::{complete_state_advance, partial_state_advance}; use state_processing::AllCaches; @@ -58,6 +57,7 @@ use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{BeaconState, EthSpec, Hash256}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/lcli/src/state_root.rs b/lcli/src/state_root.rs index 17a947b2f0..b2308999d4 100644 --- a/lcli/src/state_root.rs +++ b/lcli/src/state_root.rs @@ -4,9 +4,9 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{BeaconState, EthSpec}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/lcli/src/transition_blocks.rs b/lcli/src/transition_blocks.rs index ecfa04fc81..4831f86491 100644 --- a/lcli/src/transition_blocks.rs +++ b/lcli/src/transition_blocks.rs @@ -72,8 +72,6 @@ use eth2::{ BeaconNodeHttpClient, SensitiveUrl, Timeouts, }; use eth2_network_config::Eth2NetworkConfig; -use log::{debug, info}; -use sloggers::{null::NullLoggerBuilder, Build}; use ssz::Encode; use state_processing::state_advance::complete_state_advance; use state_processing::{ @@ -87,6 +85,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, Instant}; use store::HotColdDB; +use tracing::{debug, info}; use types::{BeaconState, ChainSpec, EthSpec, Hash256, SignedBeaconBlock}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); @@ -196,14 +195,8 @@ pub fn run( * Create a `BeaconStore` and `ValidatorPubkeyCache` for block signature verification. */ - let store = HotColdDB::open_ephemeral( - <_>::default(), - spec.clone(), - NullLoggerBuilder - .build() - .map_err(|e| format!("Error on NullLoggerBuilder: {:?}", e))?, - ) - .map_err(|e| format!("Failed to create ephemeral store: {:?}", e))?; + let store = HotColdDB::open_ephemeral(<_>::default(), spec.clone()) + .map_err(|e| format!("Failed to create ephemeral store: {:?}", e))?; let store = Arc::new(store); debug!("Building pubkey cache (might take some time)"); diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index c95735d41c..3774a9c458 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lighthouse" -version = "6.0.1" +version = "7.0.0-beta.5" authors = ["Sigma Prime "] edition = { workspace = true } autotests = false @@ -60,9 +60,10 @@ serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } slasher = { workspace = true } -slog = { workspace = true } store = { workspace = true } task_executor = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } unused_port = { workspace = true } validator_client = { workspace = true } diff --git a/lighthouse/environment/Cargo.toml b/lighthouse/environment/Cargo.toml index 02b8e0b655..6d6ffa1725 100644 --- a/lighthouse/environment/Cargo.toml +++ b/lighthouse/environment/Cargo.toml @@ -6,18 +6,19 @@ edition = { workspace = true } [dependencies] async-channel = { workspace = true } +clap = { workspace = true } eth2_config = { workspace = true } eth2_network_config = { workspace = true } futures = { workspace = true } logging = { workspace = true } +logroller = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -slog-async = { workspace = true } -slog-json = "2.3.0" -slog-term = { workspace = true } -sloggers = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } [target.'cfg(not(target_family = "unix"))'.dependencies] diff --git a/lighthouse/environment/src/lib.rs b/lighthouse/environment/src/lib.rs index 89d759d662..f427836751 100644 --- a/lighthouse/environment/src/lib.rs +++ b/lighthouse/environment/src/lib.rs @@ -11,31 +11,38 @@ use eth2_config::Eth2Config; use eth2_network_config::Eth2NetworkConfig; use futures::channel::mpsc::{channel, Receiver, Sender}; use futures::{future, StreamExt}; - -use logging::{test_logger, SSELoggingComponents}; +use logging::tracing_logging_layer::LoggingLayer; +use logging::SSELoggingComponents; +use logroller::{Compression, LogRollerBuilder, Rotation, RotationSize}; use serde::{Deserialize, Serialize}; -use slog::{error, info, o, warn, Drain, Duplicate, Level, Logger}; -use sloggers::{file::FileLoggerBuilder, types::Format, types::Severity, Build}; -use std::fs::create_dir_all; -use std::io::{Result as IOResult, Write}; use std::path::PathBuf; use std::sync::Arc; use task_executor::{ShutdownReason, TaskExecutor}; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; +use tracing::{error, info, warn}; +use tracing_subscriber::filter::LevelFilter; use types::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; #[cfg(target_family = "unix")] use { futures::Future, - std::{pin::Pin, task::Context, task::Poll}, + std::{ + fs::{read_dir, set_permissions, Permissions}, + os::unix::fs::PermissionsExt, + path::Path, + pin::Pin, + task::Context, + task::Poll, + }, tokio::signal::unix::{signal, Signal, SignalKind}, }; #[cfg(not(target_family = "unix"))] use {futures::channel::oneshot, std::cell::RefCell}; -const LOG_CHANNEL_SIZE: usize = 16384; -const SSE_LOG_CHANNEL_SIZE: usize = 2048; +pub mod tracing_common; + +pub const SSE_LOG_CHANNEL_SIZE: usize = 2048; /// The maximum time in seconds the client will wait for all internal tasks to shutdown. const MAXIMUM_SHUTDOWN_TIME: u64 = 15; @@ -47,37 +54,54 @@ const MAXIMUM_SHUTDOWN_TIME: u64 = 15; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggerConfig { pub path: Option, - pub debug_level: String, - pub logfile_debug_level: String, + #[serde(skip_serializing, skip_deserializing, default = "default_debug_level")] + pub debug_level: LevelFilter, + #[serde( + skip_serializing, + skip_deserializing, + default = "default_logfile_debug_level" + )] + pub logfile_debug_level: LevelFilter, pub log_format: Option, pub logfile_format: Option, pub log_color: bool, + pub logfile_color: bool, pub disable_log_timestamp: bool, pub max_log_size: u64, pub max_log_number: usize, pub compression: bool, pub is_restricted: bool, pub sse_logging: bool, + pub extra_info: bool, } impl Default for LoggerConfig { fn default() -> Self { LoggerConfig { path: None, - debug_level: String::from("info"), - logfile_debug_level: String::from("debug"), + debug_level: LevelFilter::INFO, + logfile_debug_level: LevelFilter::DEBUG, log_format: None, + log_color: true, logfile_format: None, - log_color: false, + logfile_color: false, disable_log_timestamp: false, max_log_size: 200, max_log_number: 5, compression: false, is_restricted: true, sse_logging: false, + extra_info: false, } } } +fn default_debug_level() -> LevelFilter { + LevelFilter::INFO +} + +fn default_logfile_debug_level() -> LevelFilter { + LevelFilter::DEBUG +} /// An execution context that can be used by a service. /// /// Distinct from an `Environment` because a `Context` is not able to give a mutable reference to a @@ -109,17 +133,11 @@ impl RuntimeContext { pub fn eth2_config(&self) -> &Eth2Config { &self.eth2_config } - - /// Returns a reference to the logger for this service. - pub fn log(&self) -> &slog::Logger { - self.executor.log() - } } /// Builds an `Environment`. pub struct EnvironmentBuilder { runtime: Option>, - log: Option, sse_logging_components: Option, eth_spec_instance: E, eth2_config: Eth2Config, @@ -131,7 +149,6 @@ impl EnvironmentBuilder { pub fn minimal() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: MinimalEthSpec, eth2_config: Eth2Config::minimal(), @@ -145,7 +162,6 @@ impl EnvironmentBuilder { pub fn mainnet() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: MainnetEthSpec, eth2_config: Eth2Config::mainnet(), @@ -159,7 +175,6 @@ impl EnvironmentBuilder { pub fn gnosis() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: GnosisEthSpec, eth2_config: Eth2Config::gnosis(), @@ -182,149 +197,123 @@ impl EnvironmentBuilder { Ok(self) } - /// Sets a logger suitable for test usage. - pub fn test_logger(mut self) -> Result { - self.log = Some(test_logger()); - Ok(self) - } + pub fn init_tracing( + mut self, + config: LoggerConfig, + logfile_prefix: &str, + ) -> ( + Self, + LoggingLayer, + LoggingLayer, + Option, + ) { + let filename_prefix = match logfile_prefix { + "beacon_node" => "beacon", + "validator_client" => "validator", + _ => logfile_prefix, + }; - fn log_nothing(_: &mut dyn Write) -> IOResult<()> { - Ok(()) - } + #[cfg(target_family = "unix")] + let file_mode = if config.is_restricted { 0o600 } else { 0o644 }; - /// Initializes the logger using the specified configuration. - /// The logger is "async" because it has a dedicated thread that accepts logs and then - /// asynchronously flushes them to stdout/files/etc. This means the thread that raised the log - /// does not have to wait for the logs to be flushed. - /// The logger can be duplicated and more detailed logs can be output to `logfile`. - /// Note that background file logging will spawn a new thread. - pub fn initialize_logger(mut self, config: LoggerConfig) -> Result { - // Setting up the initial logger format and build it. - let stdout_drain = if let Some(ref format) = config.log_format { - match format.to_uppercase().as_str() { - "JSON" => { - let stdout_drain = slog_json::Json::default(std::io::stdout()).fuse(); - slog_async::Async::new(stdout_drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() + let file_logging_layer = { + if let Some(path) = config.path { + let mut appender = LogRollerBuilder::new( + path.clone(), + PathBuf::from(format!("{}.log", filename_prefix)), + ) + .rotation(Rotation::SizeBased(RotationSize::MB(config.max_log_size))) + .max_keep_files(config.max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + if config.compression { + appender = appender.compression(Compression::Gzip); } - _ => return Err("Logging format provided is not supported".to_string()), - } - } else { - let stdout_decorator_builder = slog_term::TermDecorator::new(); - let stdout_decorator = if config.log_color { - stdout_decorator_builder.force_color() - } else { - stdout_decorator_builder - } - .build(); - let stdout_decorator = - logging::AlignedTermDecorator::new(stdout_decorator, logging::MAX_MESSAGE_WIDTH); - let stdout_drain = slog_term::FullFormat::new(stdout_decorator); - let stdout_drain = if config.disable_log_timestamp { - stdout_drain.use_custom_timestamp(Self::log_nothing) - } else { - stdout_drain - } - .build() - .fuse(); - slog_async::Async::new(stdout_drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() - }; + match appender.build() { + Ok(file_appender) => { + #[cfg(target_family = "unix")] + set_logfile_permissions(&path, filename_prefix, file_mode); - let stdout_drain = match config.debug_level.as_str() { - "info" => stdout_drain.filter_level(Level::Info), - "debug" => stdout_drain.filter_level(Level::Debug), - "trace" => stdout_drain.filter_level(Level::Trace), - "warn" => stdout_drain.filter_level(Level::Warning), - "error" => stdout_drain.filter_level(Level::Error), - "crit" => stdout_drain.filter_level(Level::Critical), - unknown => return Err(format!("Unknown debug-level: {}", unknown)), - }; + let (file_non_blocking_writer, file_guard) = + tracing_appender::non_blocking(file_appender); - let stdout_logger = Logger::root(stdout_drain.fuse(), o!()); - - // Disable file logging if values set to 0. - if config.max_log_size == 0 || config.max_log_number == 0 { - self.log = Some(stdout_logger); - return Ok(self); - } - - // Disable file logging if no path is specified. - let Some(path) = config.path else { - self.log = Some(stdout_logger); - return Ok(self); - }; - - // Ensure directories are created becfore the logfile. - if !path.exists() { - let mut dir = path.clone(); - dir.pop(); - - // Create the necessary directories for the correct service and network. - if !dir.exists() { - let res = create_dir_all(dir); - - // If the directories cannot be created, warn and disable the logger. - match res { - Ok(_) => (), + LoggingLayer::new( + file_non_blocking_writer, + file_guard, + config.disable_log_timestamp, + false, + config.logfile_color, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + false, + ) + } Err(e) => { - let log = stdout_logger; - warn!( - log, - "Background file logging is disabled"; - "error" => e); - self.log = Some(log); - return Ok(self); + eprintln!("Failed to initialize rolling file appender: {}", e); + let (sink_writer, sink_guard) = + tracing_appender::non_blocking(std::io::sink()); + LoggingLayer::new( + sink_writer, + sink_guard, + config.disable_log_timestamp, + false, + config.logfile_color, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + false, + ) } } + } else { + eprintln!("No path provided. File logging is disabled."); + let (sink_writer, sink_guard) = tracing_appender::non_blocking(std::io::sink()); + LoggingLayer::new( + sink_writer, + sink_guard, + config.disable_log_timestamp, + false, + true, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + false, + ) } - } - - let logfile_level = match config.logfile_debug_level.as_str() { - "info" => Severity::Info, - "debug" => Severity::Debug, - "trace" => Severity::Trace, - "warn" => Severity::Warning, - "error" => Severity::Error, - "crit" => Severity::Critical, - unknown => return Err(format!("Unknown loglevel-debug-level: {}", unknown)), }; - let file_logger = FileLoggerBuilder::new(&path) - .level(logfile_level) - .channel_size(LOG_CHANNEL_SIZE) - .format(match config.logfile_format.as_deref() { - Some("JSON") => Format::Json, - _ => Format::default(), - }) - .rotate_size(config.max_log_size) - .rotate_keep(config.max_log_number) - .rotate_compress(config.compression) - .restrict_permissions(config.is_restricted) - .build() - .map_err(|e| format!("Unable to build file logger: {}", e))?; + let (stdout_non_blocking_writer, stdout_guard) = + tracing_appender::non_blocking(std::io::stdout()); - let mut log = Logger::root(Duplicate::new(stdout_logger, file_logger).fuse(), o!()); - - info!( - log, - "Logging to file"; - "path" => format!("{:?}", path) + let stdout_logging_layer = LoggingLayer::new( + stdout_non_blocking_writer, + stdout_guard, + config.disable_log_timestamp, + config.log_color, + true, + config.log_format, + config.logfile_format, + config.extra_info, + false, ); - // If the http API is enabled, we may need to send logs to be consumed by subscribers. - if config.sse_logging { - let sse_logger = SSELoggingComponents::new(SSE_LOG_CHANNEL_SIZE); - self.sse_logging_components = Some(sse_logger.clone()); + let sse_logging_layer_opt = if config.sse_logging { + Some(SSELoggingComponents::new(SSE_LOG_CHANNEL_SIZE)) + } else { + None + }; - log = Logger::root(Duplicate::new(log, sse_logger).fuse(), o!()); - } + self.sse_logging_components = sse_logging_layer_opt.clone(); - self.log = Some(log); - - Ok(self) + ( + self, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + ) } /// Adds a network configuration to the environment. @@ -351,7 +340,6 @@ impl EnvironmentBuilder { signal_rx: Some(signal_rx), signal: Some(signal), exit, - log: self.log.ok_or("Cannot build environment without log")?, sse_logging_components: self.sse_logging_components, eth_spec_instance: self.eth_spec_instance, eth2_config: self.eth2_config, @@ -370,7 +358,6 @@ pub struct Environment { signal_tx: Sender, signal: Option>, exit: async_channel::Receiver<()>, - log: Logger, sse_logging_components: Option, eth_spec_instance: E, pub eth2_config: Eth2Config, @@ -386,14 +373,14 @@ impl Environment { &self.runtime } - /// Returns a `Context` where no "service" has been added to the logger output. + /// Returns a `Context` where a "core" service has been added to the logger output. pub fn core_context(&self) -> RuntimeContext { RuntimeContext { executor: TaskExecutor::new( Arc::downgrade(self.runtime()), self.exit.clone(), - self.log.clone(), self.signal_tx.clone(), + "core".to_string(), ), eth_spec_instance: self.eth_spec_instance.clone(), eth2_config: self.eth2_config.clone(), @@ -408,8 +395,8 @@ impl Environment { executor: TaskExecutor::new( Arc::downgrade(self.runtime()), self.exit.clone(), - self.log.new(o!("service" => service_name)), self.signal_tx.clone(), + service_name, ), eth_spec_instance: self.eth_spec_instance.clone(), eth2_config: self.eth2_config.clone(), @@ -441,7 +428,7 @@ impl Environment { let terminate = SignalFuture::new(terminate_stream, "Received SIGTERM"); handles.push(terminate); } - Err(e) => error!(self.log, "Could not register SIGTERM handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGTERM handler"), }; // setup for handling SIGINT @@ -450,7 +437,7 @@ impl Environment { let interrupt = SignalFuture::new(interrupt_stream, "Received SIGINT"); handles.push(interrupt); } - Err(e) => error!(self.log, "Could not register SIGINT handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGINT handler"), } // setup for handling a SIGHUP @@ -459,7 +446,7 @@ impl Environment { let hup = SignalFuture::new(hup_stream, "Received SIGHUP"); handles.push(hup); } - Err(e) => error!(self.log, "Could not register SIGHUP handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGHUP handler"), } future::select(inner_shutdown, future::select_all(handles.into_iter())).await @@ -467,7 +454,7 @@ impl Environment { match self.runtime().block_on(register_handlers) { future::Either::Left((Ok(reason), _)) => { - info!(self.log, "Internal shutdown received"; "reason" => reason.message()); + info!("Internal shutdown received"); Ok(reason) } future::Either::Left((Err(e), _)) => Err(e.into()), @@ -494,14 +481,12 @@ impl Environment { // setup for handling a Ctrl-C let (ctrlc_send, ctrlc_oneshot) = oneshot::channel(); let ctrlc_send_c = RefCell::new(Some(ctrlc_send)); - let log = self.log.clone(); ctrlc::set_handler(move || { if let Some(ctrlc_send) = ctrlc_send_c.try_borrow_mut().unwrap().take() { if let Err(e) = ctrlc_send.send(()) { error!( - log, - "Error sending ctrl-c message"; - "error" => e + error = ?e, + "Error sending ctrl-c message" ); } } @@ -514,7 +499,7 @@ impl Environment { .block_on(future::select(inner_shutdown, ctrlc_oneshot)) { future::Either::Left((Ok(reason), _)) => { - info!(self.log, "Internal shutdown received"; "reason" => reason.message()); + info!(reason = reason.message(), "Internal shutdown received"); Ok(reason) } future::Either::Left((Err(e), _)) => Err(e.into()), @@ -531,9 +516,8 @@ impl Environment { runtime.shutdown_timeout(std::time::Duration::from_secs(MAXIMUM_SHUTDOWN_TIME)) } Err(e) => warn!( - self.log, - "Failed to obtain runtime access to shutdown gracefully"; - "error" => ?e + error = ?e, + "Failed to obtain runtime access to shutdown gracefully" ), } } @@ -579,3 +563,37 @@ impl Future for SignalFuture { } } } + +#[cfg(target_family = "unix")] +fn set_logfile_permissions(log_dir: &Path, filename_prefix: &str, file_mode: u32) { + let newest = read_dir(log_dir) + .ok() + .into_iter() + .flat_map(|entries| entries.filter_map(Result::ok)) + .filter_map(|entry| { + let path = entry.path(); + let fname = path.file_name()?.to_string_lossy(); + if path.is_file() && fname.starts_with(filename_prefix) && fname.ends_with(".log") { + let modified = entry.metadata().ok()?.modified().ok()?; + Some((path, modified)) + } else { + None + } + }) + .max_by_key(|(_path, mtime)| *mtime); + + match newest { + Some((file, _mtime)) => { + if let Err(e) = set_permissions(&file, Permissions::from_mode(file_mode)) { + eprintln!("Failed to set permissions on {}: {}", file.display(), e); + } + } + None => { + eprintln!( + "Couldn't find a newly created logfile in {} matching prefix \"{}\".", + log_dir.display(), + filename_prefix + ); + } + } +} diff --git a/lighthouse/environment/src/tracing_common.rs b/lighthouse/environment/src/tracing_common.rs new file mode 100644 index 0000000000..893f50dae5 --- /dev/null +++ b/lighthouse/environment/src/tracing_common.rs @@ -0,0 +1,72 @@ +use crate::{EnvironmentBuilder, LoggerConfig}; +use clap::ArgMatches; +use logging::Libp2pDiscv5TracingLayer; +use logging::{tracing_logging_layer::LoggingLayer, SSELoggingComponents}; +use std::process; +use tracing_subscriber::filter::{FilterFn, LevelFilter}; +use types::EthSpec; + +pub fn construct_logger( + logger_config: LoggerConfig, + matches: &ArgMatches, + environment_builder: EnvironmentBuilder, +) -> ( + EnvironmentBuilder, + Libp2pDiscv5TracingLayer, + LoggingLayer, + LoggingLayer, + Option, + LoggerConfig, + FilterFn, +) { + let libp2p_discv5_layer = logging::create_libp2p_discv5_tracing_layer( + logger_config.path.clone(), + logger_config.max_log_size, + logger_config.compression, + logger_config.max_log_number, + ); + + let logfile_prefix = matches.subcommand_name().unwrap_or("lighthouse"); + + let (builder, file_logging_layer, stdout_logging_layer, sse_logging_layer_opt) = + environment_builder.init_tracing(logger_config.clone(), logfile_prefix); + + let dependency_log_filter = + FilterFn::new(filter_dependency_log as fn(&tracing::Metadata<'_>) -> bool); + + ( + builder, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) +} + +pub fn parse_level(level: &str) -> LevelFilter { + match level.to_lowercase().as_str() { + "error" => LevelFilter::ERROR, + "warn" => LevelFilter::WARN, + "info" => LevelFilter::INFO, + "debug" => LevelFilter::DEBUG, + "trace" => LevelFilter::TRACE, + _ => { + eprintln!("Unsupported log level"); + process::exit(1) + } + } +} + +fn filter_dependency_log(meta: &tracing::Metadata<'_>) -> bool { + if let Some(file) = meta.file() { + let target = meta.target(); + if file.contains("/.cargo/") { + return target.contains("discv5") || target.contains("libp2p"); + } else { + return !file.contains("gossipsub") && !target.contains("hyper"); + } + } + true +} diff --git a/lighthouse/environment/tests/environment_builder.rs b/lighthouse/environment/tests/environment_builder.rs index b0c847612a..a98caf8df5 100644 --- a/lighthouse/environment/tests/environment_builder.rs +++ b/lighthouse/environment/tests/environment_builder.rs @@ -9,8 +9,6 @@ fn builder() -> EnvironmentBuilder { EnvironmentBuilder::mainnet() .multi_threaded_tokio_runtime() .expect("should set runtime") - .test_logger() - .expect("should set logger") } fn eth2_network_config() -> Option { diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index d7a14e3809..2b7387e076 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -11,18 +11,23 @@ use clap_utils::{ }; use cli::LighthouseSubcommands; use directory::{parse_path_or_default, DEFAULT_BEACON_NODE_DIR, DEFAULT_VALIDATOR_DIR}; +use environment::tracing_common; use environment::{EnvironmentBuilder, LoggerConfig}; use eth2_network_config::{Eth2NetworkConfig, DEFAULT_HARDCODED_NETWORK, HARDCODED_NET_NAMES}; use ethereum_hashing::have_sha_extensions; use futures::TryFutureExt; use lighthouse_version::VERSION; +use logging::crit; +use logging::MetricsLayer; use malloc_utils::configure_memory_allocator; -use slog::{crit, info}; use std::backtrace::Backtrace; use std::path::PathBuf; use std::process::exit; use std::sync::LazyLock; use task_executor::ShutdownReason; +use tracing::{info, warn}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use types::{EthSpec, EthSpecId}; use validator_client::ProductionValidatorClient; @@ -120,15 +125,11 @@ fn main() { .display_order(0), ) .arg( - Arg::new("logfile") - .long("logfile") - .value_name("FILE") + Arg::new("logfile-dir") + .long("logfile-dir") + .value_name("DIR") .help( - "File path where the log file will be stored. Once it grows to the \ - value specified in `--logfile-max-size` a new log file is generated where \ - future logs are stored. \ - Once the number of log files exceeds the value specified in \ - `--logfile-max-number` the oldest log file will be overwritten.") + "Directory path where the log file will be stored") .action(ArgAction::Set) .global(true) .display_order(0) @@ -139,7 +140,7 @@ fn main() { .value_name("LEVEL") .help("The verbosity level used when emitting logs to the log file.") .action(ArgAction::Set) - .value_parser(["info", "debug", "trace", "warn", "error", "crit"]) + .value_parser(["info", "debug", "trace", "warn", "error"]) .default_value("debug") .global(true) .display_order(0) @@ -215,13 +216,36 @@ fn main() { .arg( Arg::new("log-color") .long("log-color") - .alias("log-colour") - .help("Force outputting colors when emitting logs to the terminal.") + .alias("log-color") + .help("Enables/Disables colors for logs in terminal. \ + Set it to false to disable colors.") + .num_args(0..=1) + .default_missing_value("true") + .default_value("true") + .value_parser(clap::value_parser!(bool)) + .help_heading(FLAG_HEADER) + .global(true) + .display_order(0) + ) + .arg( + Arg::new("logfile-color") + .long("logfile-color") + .alias("logfile-colour") + .help("Enables colors in logfile.") .action(ArgAction::SetTrue) .help_heading(FLAG_HEADER) .global(true) .display_order(0) ) + .arg( + Arg::new("log-extra-info") + .long("log-extra-info") + .action(ArgAction::SetTrue) + .help_heading(FLAG_HEADER) + .help("If present, show module,file,line in logs") + .global(true) + .display_order(0) + ) .arg( Arg::new("disable-log-timestamp") .long("disable-log-timestamp") @@ -237,7 +261,7 @@ fn main() { .value_name("LEVEL") .help("Specifies the verbosity level used when emitting logs to the terminal.") .action(ArgAction::Set) - .value_parser(["info", "debug", "trace", "warn", "error", "crit"]) + .value_parser(["info", "debug", "trace", "warn", "error"]) .global(true) .default_value("info") .display_order(0) @@ -499,10 +523,16 @@ fn run( let log_format = matches.get_one::("log-format"); - let log_color = matches.get_flag("log-color"); + let log_color = matches + .get_one::("log-color") + .copied() + .unwrap_or(true); + + let logfile_color = matches.get_flag("logfile-color"); let disable_log_timestamp = matches.get_flag("disable-log-timestamp"); + let extra_info = matches.get_flag("log-extra-info"); let logfile_debug_level = matches .get_one::("logfile-debug-level") .ok_or("Expected --logfile-debug-level flag")?; @@ -529,15 +559,13 @@ fn run( let logfile_restricted = !matches.get_flag("logfile-no-restricted-perms"); // Construct the path to the log file. - let mut log_path: Option = clap_utils::parse_optional(matches, "logfile")?; + let mut log_path: Option = clap_utils::parse_optional(matches, "logfile-dir")?; if log_path.is_none() { log_path = match matches.subcommand() { Some(("beacon_node", _)) => Some( parse_path_or_default(matches, "datadir")? .join(DEFAULT_BEACON_NODE_DIR) - .join("logs") - .join("beacon") - .with_extension("log"), + .join("logs"), ), Some(("validator_client", vc_matches)) => { let base_path = if vc_matches.contains_id("validators-dir") { @@ -546,12 +574,7 @@ fn run( parse_path_or_default(matches, "datadir")?.join(DEFAULT_VALIDATOR_DIR) }; - Some( - base_path - .join("logs") - .join("validator") - .with_extension("log"), - ) + Some(base_path.join("logs")) } _ => None, }; @@ -567,57 +590,81 @@ fn run( } }; - let logger_config = LoggerConfig { - path: log_path.clone(), - debug_level: String::from(debug_level), - logfile_debug_level: String::from(logfile_debug_level), - log_format: log_format.map(String::from), - logfile_format: logfile_format.map(String::from), - log_color, - disable_log_timestamp, - max_log_size: logfile_max_size * 1_024 * 1_024, - max_log_number: logfile_max_number, - compression: logfile_compress, - is_restricted: logfile_restricted, - sse_logging, + let ( + builder, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { + path: log_path.clone(), + debug_level: tracing_common::parse_level(debug_level), + logfile_debug_level: tracing_common::parse_level(logfile_debug_level), + log_format: log_format.map(String::from), + logfile_format: logfile_format.map(String::from), + log_color, + logfile_color, + disable_log_timestamp, + max_log_size: logfile_max_size, + max_log_number: logfile_max_number, + compression: logfile_compress, + is_restricted: logfile_restricted, + sse_logging, + extra_info, + }, + matches, + environment_builder, + ); + + let logging = tracing_subscriber::registry() + .with(dependency_log_filter) + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(MetricsLayer) + .with(libp2p_discv5_layer); + + let logging_result = if let Some(sse_logging_layer) = sse_logging_layer_opt { + logging.with(sse_logging_layer).try_init() + } else { + logging.try_init() }; - let builder = environment_builder.initialize_logger(logger_config.clone())?; + if let Err(e) = logging_result { + eprintln!("Failed to initialize dependency logging: {e}"); + } let mut environment = builder .multi_threaded_tokio_runtime()? .eth2_network_config(eth2_network_config)? .build()?; - let log = environment.core_context().log().clone(); - // Log panics properly. { - let log = log.clone(); std::panic::set_hook(Box::new(move |info| { crit!( - log, - "Task panic. This is a bug!"; - "location" => info.location().map(ToString::to_string), - "message" => info.payload().downcast_ref::(), - "backtrace" => %Backtrace::capture(), - "advice" => "Please check above for a backtrace and notify the developers", + location = info.location().map(ToString::to_string), + message = info.payload().downcast_ref::(), + backtrace = %Backtrace::capture(), + advice = "Please check above for a backtrace and notify the developers", + "Task panic. This is a bug!" ); })); } // Allow Prometheus to export the time at which the process was started. - metrics::expose_process_start_time(&log); + metrics::expose_process_start_time(); // Allow Prometheus access to the version and commit of the Lighthouse build. metrics::expose_lighthouse_version(); #[cfg(all(feature = "modern", target_arch = "x86_64"))] if !std::is_x86_feature_detected!("adx") { - slog::warn!( - log, - "CPU seems incompatible with optimized Lighthouse build"; - "advice" => "If you get a SIGILL, please try Lighthouse portable build" + tracing::warn!( + advice = "If you get a SIGILL, please try Lighthouse portable build", + "CPU seems incompatible with optimized Lighthouse build" ); } @@ -631,7 +678,7 @@ fn run( ]; for flag in deprecated_flags { if matches.get_one::(flag).is_some() { - slog::warn!(log, "The {} flag is deprecated and does nothing", flag); + warn!("The {} flag is deprecated and does nothing", flag); } } @@ -675,26 +722,21 @@ fn run( match LighthouseSubcommands::from_arg_matches(matches) { Ok(LighthouseSubcommands::DatabaseManager(db_manager_config)) => { - info!(log, "Running database manager for {} network", network_name); + info!("Running database manager for {} network", network_name); database_manager::run(matches, &db_manager_config, environment)?; return Ok(()); } Ok(LighthouseSubcommands::ValidatorClient(validator_client_config)) => { let context = environment.core_context(); - let log = context.log().clone(); let executor = context.executor.clone(); - let config = validator_client::Config::from_cli( - matches, - &validator_client_config, - context.log(), - ) - .map_err(|e| format!("Unable to initialize validator config: {}", e))?; + let config = validator_client::Config::from_cli(matches, &validator_client_config) + .map_err(|e| format!("Unable to initialize validator config: {}", e))?; // Dump configs if `dump-config` or `dump-chain-config` flags are set clap_utils::check_dump_configs::<_, E>(matches, &config, &context.eth2_config.spec)?; let shutdown_flag = matches.get_flag("immediate-shutdown"); if shutdown_flag { - info!(log, "Validator client immediate shutdown triggered."); + info!("Validator client immediate shutdown triggered."); return Ok(()); } @@ -704,7 +746,7 @@ fn run( .and_then(|mut vc| async move { vc.start_service().await }) .await { - crit!(log, "Failed to start validator client"; "reason" => e); + crit!(reason = e, "Failed to start validator client"); // Ignore the error since it always occurs during normal operation when // shutting down. let _ = executor @@ -718,17 +760,12 @@ fn run( Err(_) => (), }; - info!(log, "Lighthouse started"; "version" => VERSION); - info!( - log, - "Configured for network"; - "name" => &network_name - ); + info!(version = VERSION, "Lighthouse started"); + info!(network_name, "Configured network"); match matches.subcommand() { Some(("beacon_node", matches)) => { let context = environment.core_context(); - let log = context.log().clone(); let executor = context.executor.clone(); let mut config = beacon_node::get_config::(matches, &context)?; config.logger_config = logger_config; @@ -737,29 +774,14 @@ fn run( let shutdown_flag = matches.get_flag("immediate-shutdown"); if shutdown_flag { - info!(log, "Beacon node immediate shutdown triggered."); + info!("Beacon node immediate shutdown triggered."); return Ok(()); } - let mut tracing_log_path: Option = - clap_utils::parse_optional(matches, "logfile")?; - - if tracing_log_path.is_none() { - tracing_log_path = Some( - parse_path_or_default(matches, "datadir")? - .join(DEFAULT_BEACON_NODE_DIR) - .join("logs"), - ) - } - - let path = tracing_log_path.clone().unwrap(); - - logging::create_tracing_layer(path); - executor.clone().spawn( async move { if let Err(e) = ProductionBeaconNode::new(context.clone(), config).await { - crit!(log, "Failed to start beacon node"; "reason" => e); + crit!(reason = ?e, "Failed to start beacon node"); // Ignore the error since it always occurs during normal operation when // shutting down. let _ = executor @@ -774,14 +796,14 @@ fn run( // Qt the moment this needs to exist so that we dont trigger a crit. Some(("validator_client", _)) => (), _ => { - crit!(log, "No subcommand supplied. See --help ."); + crit!("No subcommand supplied. See --help ."); return Err("No subcommand supplied.".into()); } }; // Block this thread until we get a ctrl-c or a task sends a shutdown signal. let shutdown_reason = environment.block_until_shutdown_requested()?; - info!(log, "Shutting down.."; "reason" => ?shutdown_reason); + info!(reason = ?shutdown_reason, "Shutting down.."); environment.fire_signal(); diff --git a/lighthouse/src/metrics.rs b/lighthouse/src/metrics.rs index 30e0120582..6b464a18be 100644 --- a/lighthouse/src/metrics.rs +++ b/lighthouse/src/metrics.rs @@ -1,8 +1,8 @@ use lighthouse_version::VERSION; pub use metrics::*; -use slog::{error, Logger}; use std::sync::LazyLock; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::error; pub static PROCESS_START_TIME_SECONDS: LazyLock> = LazyLock::new(|| { try_create_int_gauge( @@ -19,13 +19,12 @@ pub static LIGHTHOUSE_VERSION: LazyLock> = LazyLock::new(|| ) }); -pub fn expose_process_start_time(log: &Logger) { +pub fn expose_process_start_time() { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(duration) => set_gauge(&PROCESS_START_TIME_SECONDS, duration.as_secs() as i64), Err(e) => error!( - log, - "Failed to read system time"; - "error" => %e + error = %e, + "Failed to read system time" ), } } diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 03314930b9..1fb9e40c23 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1,7 +1,7 @@ use crate::exec::{CommandLineTestExec, CompletedTest}; use beacon_node::beacon_chain::chain_config::{ DisallowedReOrgOffsets, DEFAULT_RE_ORG_CUTOFF_DENOMINATOR, DEFAULT_RE_ORG_HEAD_THRESHOLD, - DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, + DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, DEFAULT_SYNC_TOLERANCE_EPOCHS, }; use beacon_node::{ beacon_chain::graffiti_calculator::GraffitiOrigin, @@ -720,6 +720,40 @@ fn builder_user_agent() { ); } +#[test] +fn test_builder_disable_ssz_flag() { + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + None, + None, + |config| { + assert!( + !config + .execution_layer + .as_ref() + .unwrap() + .disable_builder_ssz_requests, + ); + }, + ); + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + Some("builder-disable-ssz"), + None, + |config| { + assert!( + config + .execution_layer + .as_ref() + .unwrap() + .disable_builder_ssz_requests, + ); + }, + ); +} + fn run_jwt_optional_flags_test(jwt_flag: &str, jwt_id_flag: &str, jwt_version_flag: &str) { use sensitive_url::SensitiveUrl; @@ -1839,7 +1873,7 @@ fn block_cache_size_flag() { fn state_cache_size_default() { CommandLineTest::new() .run_with_zero_port() - .with_config(|config| assert_eq!(config.store.state_cache_size, new_non_zero_usize(128))); + .with_config(|config| assert_eq!(config.store.state_cache_size, new_non_zero_usize(32))); } #[test] fn state_cache_size_flag() { @@ -1849,6 +1883,21 @@ fn state_cache_size_flag() { .with_config(|config| assert_eq!(config.store.state_cache_size, new_non_zero_usize(64))); } #[test] +fn state_cache_headroom_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert_eq!(config.store.state_cache_headroom, new_non_zero_usize(1))); +} +#[test] +fn state_cache_headroom_flag() { + CommandLineTest::new() + .flag("state-cache-headroom", Some("16")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.store.state_cache_headroom, new_non_zero_usize(16)) + }); +} +#[test] fn historic_state_cache_size_flag() { CommandLineTest::new() .flag("historic-state-cache-size", Some("4")) @@ -1938,7 +1987,7 @@ fn prune_blobs_on_startup_false() { fn epochs_per_blob_prune_default() { CommandLineTest::new() .run_with_zero_port() - .with_config(|config| assert!(config.store.epochs_per_blob_prune == 1)); + .with_config(|config| assert_eq!(config.store.epochs_per_blob_prune, 256)); } #[test] fn epochs_per_blob_prune_on_startup_five() { @@ -2382,20 +2431,20 @@ fn monitoring_endpoint() { // Tests for Logger flags. #[test] -fn default_log_color_flag() { +fn default_logfile_color_flag() { CommandLineTest::new() .run_with_zero_port() .with_config(|config| { - assert!(!config.logger_config.log_color); + assert!(!config.logger_config.logfile_color); }); } #[test] -fn enabled_log_color_flag() { +fn enabled_logfile_color_flag() { CommandLineTest::new() - .flag("log-color", None) + .flag("logfile-color", None) .run_with_zero_port() .with_config(|config| { - assert!(config.logger_config.log_color); + assert!(config.logger_config.logfile_color); }); } #[test] @@ -2506,7 +2555,6 @@ fn light_client_server_default() { .with_config(|config| { assert!(config.network.enable_light_client_server); assert!(config.chain.enable_light_client_server); - assert!(config.http_api.enable_light_client_server); }); } @@ -2539,12 +2587,35 @@ fn light_client_http_server_disabled() { .flag("disable-light-client-server", None) .run_with_zero_port() .with_config(|config| { - assert!(!config.http_api.enable_light_client_server); assert!(!config.network.enable_light_client_server); assert!(!config.chain.enable_light_client_server); }); } +#[test] +fn sync_tolerance_epochs() { + CommandLineTest::new() + .flag("http", None) + .flag("sync-tolerance-epochs", Some("0")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.chain.sync_tolerance_epochs, 0); + }); +} + +#[test] +fn sync_tolerance_epochs_default() { + CommandLineTest::new() + .flag("http", None) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.sync_tolerance_epochs, + DEFAULT_SYNC_TOLERANCE_EPOCHS + ); + }); +} + #[test] fn gui_flag() { CommandLineTest::new() @@ -2715,3 +2786,69 @@ fn beacon_node_backend_override() { assert_eq!(config.store.backend, BeaconNodeBackend::LevelDb); }); } + +#[test] +fn block_publishing_delay_for_testing() { + CommandLineTest::new() + .flag("delay-block-publishing", Some("2.5")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.block_publishing_delay, + Some(Duration::from_secs_f64(2.5f64)) + ); + }); +} + +#[test] +fn data_column_publishing_delay_for_testing() { + CommandLineTest::new() + .flag("delay-data-column-publishing", Some("3.5")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.data_column_publishing_delay, + Some(Duration::from_secs_f64(3.5f64)) + ); + }); +} + +#[test] +fn invalid_block_roots_flag() { + let dir = TempDir::new().expect("Unable to create temporary directory"); + let mut file = + File::create(dir.path().join("invalid-block-roots")).expect("Unable to create file"); + file.write_all(b"2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359, 2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f358, 0x3db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f358") + .expect("Unable to write to file"); + CommandLineTest::new() + .flag( + "invalid-block-roots", + dir.path().join("invalid-block-roots").as_os_str().to_str(), + ) + .run_with_zero_port() + .with_config(|config| assert_eq!(config.chain.invalid_block_roots.len(), 3)) +} + +#[test] +fn invalid_block_roots_default_holesky() { + use beacon_node::beacon_chain::chain_config::INVALID_HOLESKY_BLOCK_ROOT; + CommandLineTest::new() + .flag("network", Some("holesky")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.chain.invalid_block_roots.len(), 1); + assert!(config + .chain + .invalid_block_roots + .contains(&*INVALID_HOLESKY_BLOCK_ROOT)); + }) +} + +#[test] +fn invalid_block_roots_default_mainnet() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + assert!(config.chain.invalid_block_roots.is_empty()); + }) +} diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index f28e7d9829..eccd97d486 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -129,6 +129,22 @@ fn use_long_timeouts_flag() { .with_config(|config| assert!(config.use_long_timeouts)); } +#[test] +fn long_timeouts_multiplier_flag_default() { + CommandLineTest::new() + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 1)); +} + +#[test] +fn long_timeouts_multiplier_flag() { + CommandLineTest::new() + .flag("use-long-timeouts", None) + .flag("long-timeouts-multiplier", Some("10")) + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 10)); +} + #[test] fn beacon_nodes_tls_certs_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); diff --git a/scripts/local_testnet/network_params.yaml b/scripts/local_testnet/network_params.yaml index b53d88e52c..87ffeb8d22 100644 --- a/scripts/local_testnet/network_params.yaml +++ b/scripts/local_testnet/network_params.yaml @@ -14,4 +14,5 @@ global_log_level: debug snooper_enabled: false additional_services: - dora + - spamoor_blob - prometheus_grafana diff --git a/scripts/local_testnet/network_params_das.yaml b/scripts/local_testnet/network_params_das.yaml index 030aa2b820..80b4bc95c6 100644 --- a/scripts/local_testnet/network_params_das.yaml +++ b/scripts/local_testnet/network_params_das.yaml @@ -4,11 +4,15 @@ participants: cl_extra_params: - --subscribe-all-data-column-subnets - --subscribe-all-subnets + # Note: useful for testing range sync (only produce block if node is in sync to prevent forking) + - --sync-tolerance-epochs=0 - --target-peers=3 count: 2 - cl_type: lighthouse cl_image: lighthouse:local cl_extra_params: + # Note: useful for testing range sync (only produce block if node is in sync to prevent forking) + - --sync-tolerance-epochs=0 - --target-peers=3 count: 2 network_params: diff --git a/scripts/tests/doppelganger_protection.sh b/scripts/tests/doppelganger_protection.sh index 5be5c13dee..80070a0791 100755 --- a/scripts/tests/doppelganger_protection.sh +++ b/scripts/tests/doppelganger_protection.sh @@ -78,7 +78,7 @@ if [[ "$BEHAVIOR" == "failure" ]]; then --files /validator_keys:$vc_1_keys_artifact_id,/testnet:el_cl_genesis_data \ $ENCLAVE_NAME $service_name $LH_IMAGE_NAME -- lighthouse \ vc \ - --debug-level debug \ + --debug-level info \ --testnet-dir=/testnet \ --validators-dir=/validator_keys/keys \ --secrets-dir=/validator_keys/secrets \ diff --git a/slasher/Cargo.toml b/slasher/Cargo.toml index fcecc2fc23..b2f6eca9c3 100644 --- a/slasher/Cargo.toml +++ b/slasher/Cargo.toml @@ -32,16 +32,14 @@ rand = { workspace = true } redb = { version = "2.1.4", optional = true } safe_arith = { workspace = true } serde = { workspace = true } -slog = { workspace = true } ssz_types = { workspace = true } strum = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } types = { workspace = true } [dev-dependencies] -logging = { workspace = true } maplit = { workspace = true } rayon = { workspace = true } tempfile = { workspace = true } - diff --git a/slasher/service/Cargo.toml b/slasher/service/Cargo.toml index 41e3b5b90a..19398fada8 100644 --- a/slasher/service/Cargo.toml +++ b/slasher/service/Cargo.toml @@ -10,9 +10,9 @@ directory = { workspace = true } lighthouse_network = { workspace = true } network = { workspace = true } slasher = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } state_processing = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/slasher/service/src/service.rs b/slasher/service/src/service.rs index 091a95dc4c..2409a24c78 100644 --- a/slasher/service/src/service.rs +++ b/slasher/service/src/service.rs @@ -8,7 +8,6 @@ use slasher::{ metrics::{self, SLASHER_DATABASE_SIZE, SLASHER_RUN_TIME}, Slasher, }; -use slog::{debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use state_processing::{ per_block_processing::errors::{ @@ -21,6 +20,7 @@ use std::sync::Arc; use task_executor::TaskExecutor; use tokio::sync::mpsc::UnboundedSender; use tokio::time::{interval_at, Duration, Instant}; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{AttesterSlashing, Epoch, EthSpec, ProposerSlashing}; pub struct SlasherService { @@ -47,9 +47,8 @@ impl SlasherService { .slasher .clone() .ok_or("No slasher is configured")?; - let log = slasher.log().clone(); - info!(log, "Starting slasher"; "broadcast" => slasher.config().broadcast); + info!(broadcast = slasher.config().broadcast, "Starting slasher"); // Buffer just a single message in the channel. If the receiver is still processing, we // don't need to burden them with more work (we can wait). @@ -65,13 +64,17 @@ impl SlasherService { update_period, slot_offset, notif_sender, - log, - ), + ) + .instrument(tracing::info_span!("slasher", service = "slasher")), "slasher_server_notifier", ); executor.spawn_blocking( - || Self::run_processor(beacon_chain, slasher, notif_receiver, network_sender), + || { + let span = info_span!("slasher", service = "slasher"); + let _ = span.enter(); + Self::run_processor(beacon_chain, slasher, notif_receiver, network_sender); + }, "slasher_server_processor", ); @@ -84,14 +87,13 @@ impl SlasherService { update_period: u64, slot_offset: f64, notif_sender: SyncSender, - log: Logger, ) { let slot_offset = Duration::from_secs_f64(slot_offset); let start_instant = if let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() { Instant::now() + duration_to_next_slot + slot_offset } else { - error!(log, "Error aligning slasher to slot clock"); + error!("Error aligning slasher to slot clock"); Instant::now() }; let mut interval = interval_at(start_instant, Duration::from_secs(update_period)); @@ -104,7 +106,7 @@ impl SlasherService { break; } } else { - trace!(log, "Slasher has nothing to do: we are pre-genesis"); + trace!("Slasher has nothing to do: we are pre-genesis"); } } } @@ -116,7 +118,6 @@ impl SlasherService { notif_receiver: Receiver, network_sender: UnboundedSender>, ) { - let log = slasher.log(); while let Ok(current_epoch) = notif_receiver.recv() { let t = Instant::now(); @@ -125,10 +126,9 @@ impl SlasherService { Ok(stats) => Some(stats), Err(e) => { error!( - log, - "Error during scheduled slasher processing"; - "epoch" => current_epoch, - "error" => ?e, + epoch = %current_epoch, + error = ?e, + "Error during scheduled slasher processing" ); None } @@ -139,10 +139,9 @@ impl SlasherService { // If the database is full then pruning could help to free it up. if let Err(e) = slasher.prune_database(current_epoch) { error!( - log, - "Error during slasher database pruning"; - "epoch" => current_epoch, - "error" => ?e, + epoch = %current_epoch, + error = ?e, + "Error during slasher database pruning" ); continue; }; @@ -155,12 +154,11 @@ impl SlasherService { if let Some(stats) = stats { debug!( - log, - "Completed slasher update"; - "epoch" => current_epoch, - "time_taken" => format!("{}ms", t.elapsed().as_millis()), - "num_attestations" => stats.attestation_stats.num_processed, - "num_blocks" => stats.block_stats.num_processed, + epoch = %current_epoch, + time_taken = format!("{}ms", t.elapsed().as_millis()), + num_attestations = stats.attestation_stats.num_processed, + num_blocks = stats.block_stats.num_processed, + "Completed slasher update" ); } } @@ -181,7 +179,6 @@ impl SlasherService { slasher: &Slasher, network_sender: &UnboundedSender>, ) { - let log = slasher.log(); let attester_slashings = slasher.get_attester_slashings(); for slashing in attester_slashings { @@ -198,18 +195,16 @@ impl SlasherService { BlockOperationError::Invalid(AttesterSlashingInvalid::NoSlashableIndices), )) => { debug!( - log, - "Skipping attester slashing for slashed validators"; - "slashing" => ?slashing, + ?slashing, + "Skipping attester slashing for slashed validators" ); continue; } Err(e) => { warn!( - log, - "Attester slashing produced is invalid"; - "error" => ?e, - "slashing" => ?slashing, + error = ?e, + ?slashing, + "Attester slashing produced is invalid" ); continue; } @@ -224,9 +219,8 @@ impl SlasherService { Self::publish_attester_slashing(beacon_chain, network_sender, slashing) { debug!( - log, - "Unable to publish attester slashing"; - "error" => e, + error = ?e, + "Unable to publish attester slashing" ); } } @@ -238,7 +232,6 @@ impl SlasherService { slasher: &Slasher, network_sender: &UnboundedSender>, ) { - let log = slasher.log(); let proposer_slashings = slasher.get_proposer_slashings(); for slashing in proposer_slashings { @@ -254,18 +247,16 @@ impl SlasherService { )), )) => { debug!( - log, - "Skipping proposer slashing for slashed validator"; - "validator_index" => index, + validator_index = index, + "Skipping proposer slashing for slashed validator" ); continue; } Err(e) => { error!( - log, - "Proposer slashing produced is invalid"; - "error" => ?e, - "slashing" => ?slashing, + error = ?e, + ?slashing, + "Proposer slashing produced is invalid" ); continue; } @@ -277,9 +268,8 @@ impl SlasherService { Self::publish_proposer_slashing(beacon_chain, network_sender, slashing) { debug!( - log, - "Unable to publish proposer slashing"; - "error" => e, + error = ?e, + "Unable to publish proposer slashing" ); } } diff --git a/slasher/src/database.rs b/slasher/src/database.rs index e2b49dca29..071109e00c 100644 --- a/slasher/src/database.rs +++ b/slasher/src/database.rs @@ -12,12 +12,12 @@ use interface::{Environment, OpenDatabases, RwTransaction}; use lru::LruCache; use parking_lot::Mutex; use serde::de::DeserializeOwned; -use slog::{info, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::borrow::{Borrow, Cow}; use std::marker::PhantomData; use std::sync::Arc; +use tracing::info; use tree_hash::TreeHash; use types::{ AggregateSignature, AttestationData, ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, @@ -287,8 +287,8 @@ fn ssz_decode(bytes: Cow<[u8]>) -> Result { } impl SlasherDB { - pub fn open(config: Arc, spec: Arc, log: Logger) -> Result { - info!(log, "Opening slasher database"; "backend" => %config.backend); + pub fn open(config: Arc, spec: Arc) -> Result { + info!(backend = %config.backend, "Opening slasher database"); std::fs::create_dir_all(&config.database_path)?; @@ -665,7 +665,7 @@ impl SlasherDB { target: Epoch, prev_max_target: Option, ) -> Result, Error> { - if prev_max_target.map_or(true, |prev_max| target > prev_max) { + if prev_max_target.is_none_or(|prev_max| target > prev_max) { return Ok(None); } diff --git a/slasher/src/slasher.rs b/slasher/src/slasher.rs index 19f2cd138d..12f35e657e 100644 --- a/slasher/src/slasher.rs +++ b/slasher/src/slasher.rs @@ -9,9 +9,9 @@ use crate::{ IndexedAttestationId, ProposerSlashingStatus, RwTransaction, SimpleBatch, SlasherDB, }; use parking_lot::Mutex; -use slog::{debug, error, info, Logger}; use std::collections::HashSet; use std::sync::Arc; +use tracing::{debug, error, info}; use types::{ AttesterSlashing, ChainSpec, Epoch, EthSpec, IndexedAttestation, ProposerSlashing, SignedBeaconBlockHeader, @@ -25,26 +25,21 @@ pub struct Slasher { attester_slashings: Mutex>>, proposer_slashings: Mutex>, config: Arc, - log: Logger, } impl Slasher { - pub fn open(config: Config, spec: Arc, log: Logger) -> Result { + pub fn open(config: Config, spec: Arc) -> Result { config.validate()?; let config = Arc::new(config); - let db = SlasherDB::open(config.clone(), spec, log.clone())?; - Self::from_config_and_db(config, db, log) + let db = SlasherDB::open(config.clone(), spec)?; + Self::from_config_and_db(config, db) } /// TESTING ONLY. /// /// Initialise a slasher database from an existing `db`. The caller must ensure that the /// database's config matches the one provided. - pub fn from_config_and_db( - config: Arc, - db: SlasherDB, - log: Logger, - ) -> Result { + pub fn from_config_and_db(config: Arc, db: SlasherDB) -> Result { config.validate()?; let attester_slashings = Mutex::new(HashSet::new()); let proposer_slashings = Mutex::new(HashSet::new()); @@ -57,7 +52,6 @@ impl Slasher { attester_slashings, proposer_slashings, config, - log, }) } @@ -80,10 +74,6 @@ impl Slasher { &self.config } - pub fn log(&self) -> &Logger { - &self.log - } - /// Accept an attestation from the network and queue it for processing. pub fn accept_attestation(&self, attestation: IndexedAttestation) { self.attestation_queue.queue(attestation); @@ -126,11 +116,7 @@ impl Slasher { let num_slashings = slashings.len(); if !slashings.is_empty() { - info!( - self.log, - "Found {} new proposer slashings!", - slashings.len(), - ); + info!("Found {} new proposer slashings!", slashings.len()); self.proposer_slashings.lock().extend(slashings); } @@ -156,11 +142,10 @@ impl Slasher { self.attestation_queue.requeue(deferred); debug!( - self.log, - "Pre-processing attestations for slasher"; - "num_valid" => num_valid, - "num_deferred" => num_deferred, - "num_dropped" => num_dropped, + %num_valid, + num_deferred, + num_dropped, + "Pre-processing attestations for slasher" ); metrics::set_gauge(&SLASHER_NUM_ATTESTATIONS_VALID, num_valid as i64); metrics::set_gauge(&SLASHER_NUM_ATTESTATIONS_DEFERRED, num_deferred as i64); @@ -194,12 +179,7 @@ impl Slasher { } } - debug!( - self.log, - "Stored attestations in slasher DB"; - "num_stored" => num_stored, - "num_valid" => num_valid, - ); + debug!(num_stored, ?num_valid, "Stored attestations in slasher DB"); metrics::set_gauge( &SLASHER_NUM_ATTESTATIONS_STORED_PER_BATCH, num_stored as i64, @@ -239,19 +219,14 @@ impl Slasher { ) { Ok(slashings) => { if !slashings.is_empty() { - info!( - self.log, - "Found {} new double-vote slashings!", - slashings.len() - ); + info!("Found {} new double-vote slashings!", slashings.len()); } self.attester_slashings.lock().extend(slashings); } Err(e) => { error!( - self.log, - "Error checking for double votes"; - "error" => format!("{:?}", e) + error = ?e, + "Error checking for double votes" ); return Err(e); } @@ -269,20 +244,12 @@ impl Slasher { ) { Ok(slashings) => { if !slashings.is_empty() { - info!( - self.log, - "Found {} new surround slashings!", - slashings.len() - ); + info!("Found {} new surround slashings!", slashings.len()); } self.attester_slashings.lock().extend(slashings); } Err(e) => { - error!( - self.log, - "Error processing array update"; - "error" => format!("{:?}", e), - ); + error!(error = ?e, "Error processing array update"); return Err(e); } } @@ -315,10 +282,9 @@ impl Slasher { if let Some(slashing) = slashing_status.into_slashing(attestation) { debug!( - self.log, - "Found double-vote slashing"; - "validator_index" => validator_index, - "epoch" => slashing.attestation_1().data().target.epoch, + validator_index, + epoch = %slashing.attestation_1().data().target.epoch, + "Found double-vote slashing" ); slashings.insert(slashing); } diff --git a/slasher/tests/attester_slashings.rs b/slasher/tests/attester_slashings.rs index cc6e57d95d..22c9cfc128 100644 --- a/slasher/tests/attester_slashings.rs +++ b/slasher/tests/attester_slashings.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use maplit::hashset; use rayon::prelude::*; use slasher::{ @@ -272,7 +271,7 @@ fn slasher_test( let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::open(config, spec).unwrap(); let current_epoch = Epoch::new(current_epoch); for (i, attestation) in attestations.iter().enumerate() { @@ -302,7 +301,7 @@ fn parallel_slasher_test( let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::open(config, spec).unwrap(); let current_epoch = Epoch::new(current_epoch); attestations diff --git a/slasher/tests/proposer_slashings.rs b/slasher/tests/proposer_slashings.rs index 6d2a1f5176..ef525c6f3f 100644 --- a/slasher/tests/proposer_slashings.rs +++ b/slasher/tests/proposer_slashings.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use slasher::{ test_utils::{block as test_block, chain_spec, E}, Config, Slasher, @@ -13,7 +12,7 @@ fn empty_pruning() { let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::::open(config, spec).unwrap(); slasher.prune_database(Epoch::new(0)).unwrap(); } @@ -27,7 +26,7 @@ fn block_pruning() { config.history_length = 2; let spec = chain_spec(); - let slasher = Slasher::::open(config.clone(), spec, test_logger()).unwrap(); + let slasher = Slasher::::open(config.clone(), spec).unwrap(); let current_epoch = Epoch::from(2 * config.history_length); // Pruning the empty database should be safe. diff --git a/slasher/tests/random.rs b/slasher/tests/random.rs index ff234dff3f..3270700d88 100644 --- a/slasher/tests/random.rs +++ b/slasher/tests/random.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use rand::prelude::*; use slasher::{ test_utils::{ @@ -36,9 +35,8 @@ impl Default for TestConfig { fn make_db() -> (TempDir, SlasherDB) { let tempdir = tempdir().unwrap(); let initial_config = Arc::new(Config::new(tempdir.path().into())); - let logger = test_logger(); let spec = chain_spec(); - let db = SlasherDB::open(initial_config.clone(), spec, logger).unwrap(); + let db = SlasherDB::open(initial_config.clone(), spec).unwrap(); (tempdir, db) } @@ -60,7 +58,7 @@ fn random_test(seed: u64, mut db: SlasherDB, test_config: TestConfig) -> Slas let config = Arc::new(config); db.update_config(config.clone()); - let slasher = Slasher::::from_config_and_db(config.clone(), db, test_logger()).unwrap(); + let slasher = Slasher::::from_config_and_db(config.clone(), db).unwrap(); let validators = (0..num_validators as u64).collect::>(); diff --git a/slasher/tests/wrap_around.rs b/slasher/tests/wrap_around.rs index 2ec56bc7d5..e34d0f2233 100644 --- a/slasher/tests/wrap_around.rs +++ b/slasher/tests/wrap_around.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use slasher::{ test_utils::{chain_spec, indexed_att}, Config, Slasher, @@ -17,7 +16,7 @@ fn attestation_pruning_empty_wrap_around() { config.chunk_size = 16; config.history_length = 16; - let slasher = Slasher::open(config.clone(), spec, test_logger()).unwrap(); + let slasher = Slasher::open(config.clone(), spec).unwrap(); let v = vec![0]; let history_length = config.history_length as u64; diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 8a662b72e3..4e744b797a 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,10 +27,8 @@ excluded_paths = [ "tests/.*/.*/ssz_static/PowBlock/", # We no longer implement merge logic. "tests/.*/bellatrix/fork_choice/on_merge_block", - # light_client - "tests/.*/.*/light_client/single_merkle_proof", + # Light client sync is not implemented "tests/.*/.*/light_client/sync", - "tests/.*/electra/light_client/update_ranking", # LightClientStore "tests/.*/.*/ssz_static/LightClientStore", # LightClientSnapshot diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index a1c74389a7..01c87b40fc 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -29,6 +29,9 @@ use types::{ IndexedAttestation, KzgProof, ProposerPreparationData, SignedBeaconBlock, Slot, Uint256, }; +// When set to true, cache any states fetched from the db. +pub const CACHE_STATE_IN_TESTS: bool = true; + #[derive(Default, Debug, PartialEq, Clone, Deserialize, Decode)] #[serde(deny_unknown_fields)] pub struct PowBlock { @@ -371,7 +374,6 @@ impl Tester { } let harness = BeaconChainHarness::>::builder(E::default()) - .logger(logging::test_logger()) .spec(spec.clone()) .keypairs(vec![]) .chain_config(ChainConfig { @@ -546,10 +548,15 @@ impl Tester { .unwrap() { let parent_state_root = parent_block.state_root(); + let mut state = self .harness .chain - .get_state(&parent_state_root, Some(parent_block.slot())) + .get_state( + &parent_state_root, + Some(parent_block.slot()), + CACHE_STATE_IN_TESTS, + ) .unwrap() .unwrap(); diff --git a/testing/ef_tests/src/cases/merkle_proof_validity.rs b/testing/ef_tests/src/cases/merkle_proof_validity.rs index 109d2cc796..711974dd43 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -20,6 +20,12 @@ pub struct MerkleProof { pub branch: Vec, } +#[derive(Debug)] +pub enum GenericMerkleProofValidity { + BeaconState(BeaconStateMerkleProofValidity), + BeaconBlockBody(Box>), +} + #[derive(Debug, Clone, Deserialize)] #[serde(bound = "E: EthSpec")] pub struct BeaconStateMerkleProofValidity { @@ -28,6 +34,39 @@ pub struct BeaconStateMerkleProofValidity { pub merkle_proof: MerkleProof, } +impl LoadCase for GenericMerkleProofValidity { + fn load_from_dir(path: &Path, fork_name: ForkName) -> Result { + let path_components = path.iter().collect::>(); + + // The "suite" name is the 2nd last directory in the path. + assert!( + path_components.len() >= 2, + "path should have at least 2 components" + ); + let suite_name = path_components[path_components.len() - 2]; + + if suite_name == "BeaconState" { + BeaconStateMerkleProofValidity::load_from_dir(path, fork_name) + .map(GenericMerkleProofValidity::BeaconState) + } else if suite_name == "BeaconBlockBody" { + BeaconBlockBodyMerkleProofValidity::load_from_dir(path, fork_name) + .map(Box::new) + .map(GenericMerkleProofValidity::BeaconBlockBody) + } else { + panic!("unsupported type for merkle proof test: {:?}", suite_name) + } + } +} + +impl Case for GenericMerkleProofValidity { + fn result(&self, case_index: usize, fork_name: ForkName) -> Result<(), Error> { + match self { + Self::BeaconState(test) => test.result(case_index, fork_name), + Self::BeaconBlockBody(test) => test.result(case_index, fork_name), + } + } +} + impl LoadCase for BeaconStateMerkleProofValidity { fn load_from_dir(path: &Path, fork_name: ForkName) -> Result { let spec = &testing_spec::(fork_name); @@ -72,11 +111,9 @@ impl Case for BeaconStateMerkleProofValidity { } }; - let Ok(proof) = proof else { - return Err(Error::FailedToParseTest( - "Could not retrieve merkle proof".to_string(), - )); - }; + let proof = proof.map_err(|e| { + Error::FailedToParseTest(format!("Could not retrieve merkle proof: {e:?}")) + })?; let proof_len = proof.len(); let branch_len = self.merkle_proof.branch.len(); if proof_len != branch_len { @@ -273,11 +310,11 @@ impl Case for BeaconBlockBodyMerkleProofValidity { fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> { let binding = self.block_body.clone(); let block_body = binding.to_ref(); - let Ok(proof) = block_body.block_body_merkle_proof(self.merkle_proof.leaf_index) else { - return Err(Error::FailedToParseTest( - "Could not retrieve merkle proof".to_string(), - )); - }; + let proof = block_body + .block_body_merkle_proof(self.merkle_proof.leaf_index) + .map_err(|e| { + Error::FailedToParseTest(format!("Could not retrieve merkle proof: {e:?}")) + })?; let proof_len = proof.len(); let branch_len = self.merkle_proof.branch.len(); if proof_len != branch_len { diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 481c9b2169..a375498239 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -1000,30 +1000,6 @@ impl Handler for KZGRecoverCellsAndKZGProofHandler { } } -#[derive(Derivative)] -#[derivative(Default(bound = ""))] -pub struct BeaconStateMerkleProofValidityHandler(PhantomData); - -impl Handler for BeaconStateMerkleProofValidityHandler { - type Case = cases::BeaconStateMerkleProofValidity; - - fn config_name() -> &'static str { - E::name() - } - - fn runner_name() -> &'static str { - "light_client" - } - - fn handler_name(&self) -> String { - "single_merkle_proof/BeaconState".into() - } - - fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - fork_name.altair_enabled() - } -} - #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct KzgInclusionMerkleProofValidityHandler(PhantomData); @@ -1054,10 +1030,10 @@ impl Handler for KzgInclusionMerkleProofValidityHandler(PhantomData); +pub struct MerkleProofValidityHandler(PhantomData); -impl Handler for BeaconBlockBodyMerkleProofValidityHandler { - type Case = cases::BeaconBlockBodyMerkleProofValidity; +impl Handler for MerkleProofValidityHandler { + type Case = cases::GenericMerkleProofValidity; fn config_name() -> &'static str { E::name() @@ -1068,11 +1044,11 @@ impl Handler for BeaconBlockBodyMerkleProofValidityHandle } fn handler_name(&self) -> String { - "single_merkle_proof/BeaconBlockBody".into() + "single_merkle_proof".into() } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - fork_name.capella_enabled() + fork_name.altair_enabled() } } diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 1f5a7dd997..3948708edf 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -955,13 +955,9 @@ fn kzg_recover_cells_and_proofs() { } #[test] -fn beacon_state_merkle_proof_validity() { - BeaconStateMerkleProofValidityHandler::::default().run(); -} - -#[test] -fn beacon_block_body_merkle_proof_validity() { - BeaconBlockBodyMerkleProofValidityHandler::::default().run(); +fn light_client_merkle_proof_validity() { + MerkleProofValidityHandler::::default().run(); + MerkleProofValidityHandler::::default().run(); } #[test] diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index f664509304..cf31c184fe 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -105,7 +105,6 @@ async fn import_and_unlock(http_url: SensitiveUrl, priv_keys: &[&str], password: impl TestRig { pub fn new(generic_engine: Engine) -> Self { - let log = logging::test_logger(); let runtime = Arc::new( tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -114,7 +113,12 @@ impl TestRig { ); let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test".to_string(), + ); let mut spec = TEST_FORK.make_genesis_spec(MainnetEthSpec::default_spec()); spec.terminal_total_difficulty = Uint256::ZERO; @@ -131,8 +135,7 @@ impl TestRig { default_datadir: execution_engine.datadir(), ..Default::default() }; - let execution_layer = - ExecutionLayer::from_config(config, executor.clone(), log.clone()).unwrap(); + let execution_layer = ExecutionLayer::from_config(config, executor.clone()).unwrap(); ExecutionPair { execution_engine, execution_layer, @@ -150,8 +153,7 @@ impl TestRig { default_datadir: execution_engine.datadir(), ..Default::default() }; - let execution_layer = - ExecutionLayer::from_config(config, executor, log.clone()).unwrap(); + let execution_layer = ExecutionLayer::from_config(config, executor).unwrap(); ExecutionPair { execution_engine, execution_layer, diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index 77645dba45..12b0afcc75 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -8,14 +8,17 @@ edition = { workspace = true } [dependencies] clap = { workspace = true } env_logger = { workspace = true } +environment = { workspace = true } eth2_network_config = { workspace = true } execution_layer = { workspace = true } futures = { workspace = true } kzg = { workspace = true } +logging = { workspace = true } node_test_rig = { path = "../node_test_rig" } parking_lot = { workspace = true } rayon = { workspace = true } sensitive_url = { path = "../../common/sensitive_url" } serde_json = { workspace = true } tokio = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 82a7028582..4cd599f845 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -13,6 +13,12 @@ use rayon::prelude::*; use std::cmp::max; use std::sync::Arc; use std::time::Duration; + +use environment::tracing_common; +use logging::MetricsLayer; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + use tokio::time::sleep; use types::{Epoch, EthSpec, MinimalEthSpec}; @@ -82,23 +88,45 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { }) .collect::>(); - let mut env = EnvironmentBuilder::minimal() - .initialize_logger(LoggerConfig { + let ( + env_builder, + _libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + _sse_logging_layer_opt, + logger_config, + _dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { path: None, - debug_level: log_level.clone(), - logfile_debug_level: log_level.clone(), + debug_level: tracing_common::parse_level(&log_level.clone()), + logfile_debug_level: tracing_common::parse_level(&log_level.clone()), log_format: None, logfile_format: None, - log_color: false, + log_color: true, + logfile_color: true, disable_log_timestamp: false, max_log_size: 0, max_log_number: 0, compression: false, is_restricted: true, sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; + extra_info: false, + }, + matches, + EnvironmentBuilder::minimal(), + ); + + if let Err(e) = tracing_subscriber::registry() + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(MetricsLayer) + .try_init() + { + eprintln!("Failed to initialize dependency logging: {e}"); + } + + let mut env = env_builder.multi_threaded_tokio_runtime()?.build()?; let mut spec = (*env.eth2_config.spec).clone(); diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index 03cc17fab3..35c2508b53 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -264,6 +264,11 @@ pub(crate) async fn verify_light_client_updates( let slot = Slot::new(slot); let previous_slot = slot - 1; + let sync_committee_period = slot + .epoch(E::slots_per_epoch()) + .sync_committee_period(&E::default_spec()) + .unwrap(); + let previous_slot_block = client .get_beacon_blocks::(BlockId::Slot(previous_slot)) .await @@ -329,6 +334,20 @@ pub(crate) async fn verify_light_client_updates( "Existing finality update too old: signature slot {signature_slot}, current slot {slot:?}" )); } + + let light_client_updates = client + .get_beacon_light_client_updates::(sync_committee_period, 1) + .await + .map_err(|e| format!("Error while getting light client update: {:?}", e))? + .ok_or(format!("Light client update not found {slot:?}"))?; + + // Ensure we're only storing a single light client update for the given sync committee period + if light_client_updates.len() != 1 { + return Err(format!( + "{} light client updates was returned when only one was expected.", + light_client_updates.len() + )); + } } Ok(()) diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 7d4bdfa264..384699c64c 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -3,7 +3,9 @@ use crate::{checks, LocalNetwork}; use clap::ArgMatches; use crate::retry::with_retry; +use environment::tracing_common; use futures::prelude::*; +use logging::MetricsLayer; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, testing_validator_config, ValidatorFiles, @@ -13,8 +15,9 @@ use std::cmp::max; use std::sync::Arc; use std::time::Duration; use tokio::time::sleep; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use types::{Epoch, EthSpec, MinimalEthSpec}; - const END_EPOCH: u64 = 16; const GENESIS_DELAY: u64 = 32; const ALTAIR_FORK_EPOCH: u64 = 0; @@ -89,23 +92,47 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { }) .collect::>(); - let mut env = EnvironmentBuilder::minimal() - .initialize_logger(LoggerConfig { + let ( + env_builder, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + _sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { path: None, - debug_level: log_level.clone(), - logfile_debug_level: log_level.clone(), + debug_level: tracing_common::parse_level(&log_level.clone()), + logfile_debug_level: tracing_common::parse_level(&log_level.clone()), log_format: None, logfile_format: None, - log_color: false, + log_color: true, + logfile_color: false, disable_log_timestamp: false, max_log_size: 0, max_log_number: 0, compression: false, is_restricted: true, sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; + extra_info: false, + }, + matches, + EnvironmentBuilder::minimal(), + ); + + if let Err(e) = tracing_subscriber::registry() + .with(dependency_log_filter) + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(libp2p_discv5_layer) + .with(MetricsLayer) + .try_init() + { + eprintln!("Failed to initialize dependency logging: {e}"); + } + + let mut env = env_builder.multi_threaded_tokio_runtime()?.build()?; let mut spec = (*env.eth2_config.spec).clone(); diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index a95c15c231..3914d33f93 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -44,7 +44,6 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) beacon_config.network.enable_light_client_server = true; beacon_config.network.discv5_config.enable_packet_filter = false; beacon_config.chain.enable_light_client_server = true; - beacon_config.http_api.enable_light_client_server = true; beacon_config.chain.optimistic_finalized_sync = false; beacon_config.trusted_setup = serde_json::from_reader(get_trusted_setup().as_slice()) .expect("Trusted setup bytes should be valid"); diff --git a/testing/test-test_logger/Cargo.toml b/testing/test-test_logger/Cargo.toml deleted file mode 100644 index d2d705f714..0000000000 --- a/testing/test-test_logger/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "test-test_logger" -version = "0.1.0" -edition = { workspace = true } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -logging = { workspace = true } -slog = { workspace = true } diff --git a/testing/test-test_logger/src/lib.rs b/testing/test-test_logger/src/lib.rs deleted file mode 100644 index a2e2a80943..0000000000 --- a/testing/test-test_logger/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -use slog::{info, Logger}; - -pub struct Config { - log: Logger, -} - -pub fn fn_with_logging(config: &Config) { - info!(&config.log, "hi"); -} - -#[cfg(test)] -mod tests { - use super::*; - use logging::test_logger; - - #[test] - fn test_fn_with_logging() { - let config = Config { log: test_logger() }; - - fn_with_logging(&config); - } -} diff --git a/testing/validator_test_rig/Cargo.toml b/testing/validator_test_rig/Cargo.toml index 76560b8afc..f28a423433 100644 --- a/testing/validator_test_rig/Cargo.toml +++ b/testing/validator_test_rig/Cargo.toml @@ -5,10 +5,9 @@ edition = { workspace = true } [dependencies] eth2 = { workspace = true } -logging = { workspace = true } mockito = { workspace = true } regex = { workspace = true } sensitive_url = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/testing/validator_test_rig/src/mock_beacon_node.rs b/testing/validator_test_rig/src/mock_beacon_node.rs index f875116155..7a90270913 100644 --- a/testing/validator_test_rig/src/mock_beacon_node.rs +++ b/testing/validator_test_rig/src/mock_beacon_node.rs @@ -1,20 +1,18 @@ use eth2::types::{GenericResponse, SyncingData}; use eth2::{BeaconNodeHttpClient, StatusCode, Timeouts}; -use logging::test_logger; use mockito::{Matcher, Mock, Server, ServerGuard}; use regex::Regex; use sensitive_url::SensitiveUrl; -use slog::{info, Logger}; use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Duration; +use tracing::info; use types::{ChainSpec, ConfigAndPreset, EthSpec, SignedBlindedBeaconBlock}; pub struct MockBeaconNode { server: ServerGuard, pub beacon_api_client: BeaconNodeHttpClient, - log: Logger, _phantom: PhantomData, pub received_blocks: Arc>>>, } @@ -27,11 +25,9 @@ impl MockBeaconNode { SensitiveUrl::from_str(&server.url()).unwrap(), Timeouts::set_all(Duration::from_secs(1)), ); - let log = test_logger(); Self { server, beacon_api_client, - log, _phantom: PhantomData, received_blocks: Arc::new(Mutex::new(Vec::new())), } @@ -69,7 +65,6 @@ impl MockBeaconNode { /// Mocks the `post_beacon_blinded_blocks_v2_ssz` response with an optional `delay`. pub fn mock_post_beacon_blinded_blocks_v2_ssz(&mut self, delay: Duration) -> Mock { let path_pattern = Regex::new(r"^/eth/v2/beacon/blinded_blocks$").unwrap(); - let log = self.log.clone(); let url = self.server.url(); let received_blocks = Arc::clone(&self.received_blocks); @@ -80,7 +75,6 @@ impl MockBeaconNode { .with_status(200) .with_body_from_request(move |request| { info!( - log, "{}", format!( "Received published block request on server {} with delay {} s", diff --git a/testing/web3signer_tests/src/get_web3signer.rs b/testing/web3signer_tests/src/get_web3signer.rs index 800feb204a..8c46a07a7d 100644 --- a/testing/web3signer_tests/src/get_web3signer.rs +++ b/testing/web3signer_tests/src/get_web3signer.rs @@ -1,65 +1,33 @@ //! This build script downloads the latest Web3Signer release and places it in the `OUT_DIR` so it //! can be used for integration testing. -use reqwest::{ - header::{self, HeaderValue}, - Client, -}; -use serde_json::Value; +use reqwest::Client; use std::env; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use zip::ZipArchive; /// Use `None` to download the latest Github release. /// Use `Some("21.8.1")` to download a specific version. const FIXED_VERSION_STRING: Option<&str> = None; -pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { - let version_file = dest_dir.join("version"); - - let client = Client::builder() - // Github gives a 403 without a user agent. - .user_agent("web3signer_tests") - .build() - .unwrap(); - +// This function no longer makes any attempt to avoid downloads, because in practice we use it +// with a fresh temp directory every time we run the tests. We might want to change this in future +// to enable reproducible/offline testing. +pub async fn download_binary(dest_dir: PathBuf) { let version = if let Some(version) = FIXED_VERSION_STRING { version.to_string() } else if let Ok(env_version) = env::var("LIGHTHOUSE_WEB3SIGNER_VERSION") { env_version } else { - // Get the latest release of the web3 signer repo. - let mut token_header_value = HeaderValue::from_str(github_token).unwrap(); - token_header_value.set_sensitive(true); - let latest_response: Value = client - .get("https://api.github.com/repos/ConsenSys/web3signer/releases/latest") - .header(header::AUTHORIZATION, token_header_value) - .send() - .await - .unwrap() - .error_for_status() - .unwrap() - .json() - .await - .unwrap(); - latest_response - .get("tag_name") - .unwrap() - .as_str() - .unwrap() - .to_string() + // The Consenys artifact server resolves `latest` to the latest release. We previously hit + // the Github API to establish the version, but that is no longer necessary. + "latest".to_string() }; + eprintln!("Downloading web3signer version: {version}"); - if version_file.exists() && fs::read(&version_file).unwrap() == version.as_bytes() { - // The latest version is already downloaded, do nothing. - return; - } else { - // Ignore the result since we don't care if the version file already exists. - let _ = fs::remove_file(&version_file); - } - - // Download the latest release zip. + // Download the release zip. + let client = Client::builder().build().unwrap(); let zip_url = format!("https://artifacts.consensys.net/public/web3signer/raw/names/web3signer.zip/versions/{}/web3signer-{}.zip", version, version); let zip_response = client .get(zip_url) @@ -73,8 +41,9 @@ pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { .unwrap(); // Write the zip to a file. - let zip_path = dest_dir.join(format!("{}.zip", version)); + let zip_path = dest_dir.join(format!("web3signer-{version}.zip")); fs::write(&zip_path, zip_response).unwrap(); + // Unzip the zip. let mut zip_file = fs::File::open(&zip_path).unwrap(); ZipArchive::new(&mut zip_file) @@ -88,15 +57,33 @@ pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { if web3signer_dir.exists() { fs::remove_dir_all(&web3signer_dir).unwrap(); } - fs::rename( - dest_dir.join(format!("web3signer-{}", version)), - web3signer_dir, - ) - .unwrap(); - // Delete zip and unzipped dir. + let versioned_web3signer_dir = find_versioned_web3signer_dir(&dest_dir); + eprintln!( + "Renaming versioned web3signer dir at: {}", + versioned_web3signer_dir.display() + ); + + fs::rename(versioned_web3signer_dir, web3signer_dir).unwrap(); + + // Delete zip. fs::remove_file(&zip_path).unwrap(); - - // Update the version file to avoid duplicate downloads. - fs::write(&version_file, version).unwrap(); +} + +fn find_versioned_web3signer_dir(dest_dir: &Path) -> PathBuf { + for entry in fs::read_dir(dest_dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + if path + .file_name() + .and_then(|n| n.to_str()) + .map(|s| s.starts_with("web3signer-")) + .unwrap_or(false) + && entry.file_type().unwrap().is_dir() + { + return path; + } + } + panic!("no directory named web3signer-* found after ZIP extraction") } diff --git a/testing/web3signer_tests/src/lib.rs b/testing/web3signer_tests/src/lib.rs index e0dee9ceb4..1eb14cf1d5 100644 --- a/testing/web3signer_tests/src/lib.rs +++ b/testing/web3signer_tests/src/lib.rs @@ -25,7 +25,6 @@ mod tests { use initialized_validators::{ load_pem_certificate, load_pkcs12_identity, InitializedValidators, }; - use logging::test_logger; use parking_lot::Mutex; use reqwest::Client; use serde::Serialize; @@ -178,14 +177,7 @@ mod tests { pub async fn new(network: &str, listen_address: &str, listen_port: u16) -> Self { GET_WEB3SIGNER_BIN .get_or_init(|| async { - // Read a Github API token from the environment. This is intended to prevent rate-limits on CI. - // We use a name that is unlikely to accidentally collide with anything the user has configured. - let github_token = env::var("LIGHTHOUSE_GITHUB_TOKEN"); - download_binary( - TEMP_DIR.lock().path().to_path_buf(), - github_token.as_deref().unwrap_or(""), - ) - .await; + download_binary(TEMP_DIR.lock().path().to_path_buf()).await; }) .await; @@ -323,7 +315,6 @@ mod tests { using_web3signer: bool, spec: Arc, ) -> Self { - let log = test_logger(); let validator_dir = TempDir::new().unwrap(); let config = initialized_validators::Config::default(); @@ -332,7 +323,6 @@ mod tests { validator_definitions, validator_dir.path().into(), config.clone(), - log.clone(), ) .await .unwrap(); @@ -347,8 +337,12 @@ mod tests { ); let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = - TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test".to_string(), + ); let slashing_db_path = validator_dir.path().join(SLASHING_PROTECTION_FILENAME); let slashing_protection = SlashingDatabase::open_or_create(&slashing_db_path).unwrap(); @@ -372,7 +366,6 @@ mod tests { slot_clock, &config, executor, - log.clone(), ); Self { diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index fb6007b00a..85517682bb 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -29,9 +29,9 @@ reqwest = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } slashing_protection = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_http_api = { workspace = true } validator_http_metrics = { workspace = true } diff --git a/validator_client/beacon_node_fallback/Cargo.toml b/validator_client/beacon_node_fallback/Cargo.toml index 598020d137..4297bae15f 100644 --- a/validator_client/beacon_node_fallback/Cargo.toml +++ b/validator_client/beacon_node_fallback/Cargo.toml @@ -15,10 +15,10 @@ eth2 = { workspace = true } futures = { workspace = true } itertools = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } strum = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_metrics = { workspace = true } diff --git a/validator_client/beacon_node_fallback/src/beacon_node_health.rs b/validator_client/beacon_node_fallback/src/beacon_node_health.rs index 80d3fb7efd..1b5d5b98cb 100644 --- a/validator_client/beacon_node_fallback/src/beacon_node_health.rs +++ b/validator_client/beacon_node_fallback/src/beacon_node_health.rs @@ -1,9 +1,9 @@ use super::CandidateError; use eth2::BeaconNodeHttpClient; use serde::{Deserialize, Serialize}; -use slog::{warn, Logger}; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; +use tracing::warn; use types::Slot; /// Sync distances between 0 and DEFAULT_SYNC_TOLERANCE are considered `synced`. @@ -276,15 +276,13 @@ impl BeaconNodeHealth { pub async fn check_node_health( beacon_node: &BeaconNodeHttpClient, - log: &Logger, ) -> Result<(Slot, bool, bool), CandidateError> { let resp = match beacon_node.get_node_syncing().await { Ok(resp) => resp, Err(e) => { warn!( - log, - "Unable connect to beacon node"; - "error" => %e + error = %e, + "Unable connect to beacon node" ); return Err(CandidateError::Offline); diff --git a/validator_client/beacon_node_fallback/src/lib.rs b/validator_client/beacon_node_fallback/src/lib.rs index abcf74a1a6..befc18c563 100644 --- a/validator_client/beacon_node_fallback/src/lib.rs +++ b/validator_client/beacon_node_fallback/src/lib.rs @@ -12,7 +12,6 @@ use environment::RuntimeContext; use eth2::BeaconNodeHttpClient; use futures::future; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; -use slog::{debug, error, warn, Logger}; use slot_clock::SlotClock; use std::cmp::Ordering; use std::fmt; @@ -24,6 +23,7 @@ use std::time::{Duration, Instant}; use std::vec::Vec; use strum::EnumVariantNames; use tokio::{sync::RwLock, time::sleep}; +use tracing::{debug, error, warn}; use types::{ChainSpec, Config as ConfigSpec, EthSpec, Slot}; use validator_metrics::{inc_counter_vec, ENDPOINT_ERRORS, ENDPOINT_REQUESTS}; @@ -222,15 +222,14 @@ impl CandidateBeaconNode { distance_tiers: &BeaconNodeSyncDistanceTiers, slot_clock: Option<&T>, spec: &ChainSpec, - log: &Logger, ) -> Result<(), CandidateError> { - if let Err(e) = self.is_compatible(spec, log).await { + if let Err(e) = self.is_compatible(spec).await { *self.health.write().await = Err(e); return Err(e); } if let Some(slot_clock) = slot_clock { - match check_node_health(&self.beacon_node, log).await { + match check_node_health(&self.beacon_node).await { Ok((head, is_optimistic, el_offline)) => { let Some(slot_clock_head) = slot_clock.now() else { let e = match slot_clock.is_prior_to_genesis() { @@ -288,17 +287,16 @@ impl CandidateBeaconNode { } /// Checks if the node has the correct specification. - async fn is_compatible(&self, spec: &ChainSpec, log: &Logger) -> Result<(), CandidateError> { + async fn is_compatible(&self, spec: &ChainSpec) -> Result<(), CandidateError> { let config = self .beacon_node .get_config_spec::() .await .map_err(|e| { error!( - log, - "Unable to read spec from beacon node"; - "error" => %e, - "endpoint" => %self.beacon_node, + error = %e, + endpoint = %self.beacon_node, + "Unable to read spec from beacon node" ); CandidateError::Offline })? @@ -306,71 +304,64 @@ impl CandidateBeaconNode { let beacon_node_spec = ChainSpec::from_config::(&config).ok_or_else(|| { error!( - log, + endpoint = %self.beacon_node, "The minimal/mainnet spec type of the beacon node does not match the validator \ - client. See the --network command."; - "endpoint" => %self.beacon_node, + client. See the --network command." + ); CandidateError::Incompatible })?; if beacon_node_spec.genesis_fork_version != spec.genesis_fork_version { error!( - log, - "Beacon node is configured for a different network"; - "endpoint" => %self.beacon_node, - "bn_genesis_fork" => ?beacon_node_spec.genesis_fork_version, - "our_genesis_fork" => ?spec.genesis_fork_version, + endpoint = %self.beacon_node, + bn_genesis_fork = ?beacon_node_spec.genesis_fork_version, + our_genesis_fork = ?spec.genesis_fork_version, + "Beacon node is configured for a different network" ); return Err(CandidateError::Incompatible); } else if beacon_node_spec.altair_fork_epoch != spec.altair_fork_epoch { warn!( - log, - "Beacon node has mismatched Altair fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_altair_fork_epoch" => ?beacon_node_spec.altair_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, + endpoint = %self.beacon_node, + endpoint_altair_fork_epoch = ?beacon_node_spec.altair_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Altair fork epoch" ); } else if beacon_node_spec.bellatrix_fork_epoch != spec.bellatrix_fork_epoch { warn!( - log, - "Beacon node has mismatched Bellatrix fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_bellatrix_fork_epoch" => ?beacon_node_spec.bellatrix_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, + endpoint = %self.beacon_node, + endpoint_bellatrix_fork_epoch = ?beacon_node_spec.bellatrix_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Bellatrix fork epoch" ); } else if beacon_node_spec.capella_fork_epoch != spec.capella_fork_epoch { warn!( - log, - "Beacon node has mismatched Capella fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_capella_fork_epoch" => ?beacon_node_spec.capella_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, + endpoint = %self.beacon_node, + endpoint_capella_fork_epoch = ?beacon_node_spec.capella_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Capella fork epoch" ); } else if beacon_node_spec.deneb_fork_epoch != spec.deneb_fork_epoch { warn!( - log, - "Beacon node has mismatched Deneb fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_deneb_fork_epoch" => ?beacon_node_spec.deneb_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, + endpoint = %self.beacon_node, + endpoint_deneb_fork_epoch = ?beacon_node_spec.deneb_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Deneb fork epoch" ); } else if beacon_node_spec.electra_fork_epoch != spec.electra_fork_epoch { warn!( - log, - "Beacon node has mismatched Electra fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_electra_fork_epoch" => ?beacon_node_spec.electra_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, + endpoint = %self.beacon_node, + endpoint_electra_fork_epoch = ?beacon_node_spec.electra_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Electra fork epoch" ); } else if beacon_node_spec.fulu_fork_epoch != spec.fulu_fork_epoch { warn!( - log, - "Beacon node has mismatched Fulu fork epoch"; - "endpoint" => %self.beacon_node, - "endpoint_fulu_fork_epoch" => ?beacon_node_spec.fulu_fork_epoch, - "hint" => UPDATE_REQUIRED_LOG_HINT, - ); + endpoint = %self.beacon_node, + endpoint_fulu_fork_epoch = ?beacon_node_spec.fulu_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Fulu fork epoch" + ); } Ok(()) @@ -387,7 +378,6 @@ pub struct BeaconNodeFallback { slot_clock: Option, broadcast_topics: Vec, spec: Arc, - log: Logger, } impl BeaconNodeFallback { @@ -396,7 +386,6 @@ impl BeaconNodeFallback { config: Config, broadcast_topics: Vec, spec: Arc, - log: Logger, ) -> Self { let distance_tiers = config.sync_tolerances; Self { @@ -405,7 +394,6 @@ impl BeaconNodeFallback { slot_clock: None, broadcast_topics, spec, - log, } } @@ -488,7 +476,6 @@ impl BeaconNodeFallback { &self.distance_tiers, self.slot_clock.as_ref(), &self.spec, - &self.log, )); nodes.push(candidate.beacon_node.to_string()); } @@ -501,10 +488,9 @@ impl BeaconNodeFallback { if let Err(e) = result { if *e != CandidateError::PreGenesis { warn!( - self.log, - "A connected beacon node errored during routine health check"; - "error" => ?e, - "endpoint" => node, + error = ?e, + endpoint = %node, + "A connected beacon node errored during routine health check" ); } } @@ -576,11 +562,7 @@ impl BeaconNodeFallback { // Run `func` using a `candidate`, returning the value or capturing errors. for candidate in candidates.iter() { - futures.push(Self::run_on_candidate( - candidate.beacon_node.clone(), - &func, - &self.log, - )); + futures.push(Self::run_on_candidate(candidate.beacon_node.clone(), &func)); } drop(candidates); @@ -598,11 +580,7 @@ impl BeaconNodeFallback { // Run `func` using a `candidate`, returning the value or capturing errors. for candidate in candidates.iter() { - futures.push(Self::run_on_candidate( - candidate.beacon_node.clone(), - &func, - &self.log, - )); + futures.push(Self::run_on_candidate(candidate.beacon_node.clone(), &func)); } drop(candidates); @@ -621,7 +599,6 @@ impl BeaconNodeFallback { async fn run_on_candidate( candidate: BeaconNodeHttpClient, func: F, - log: &Logger, ) -> Result)> where F: Fn(BeaconNodeHttpClient) -> R, @@ -636,10 +613,9 @@ impl BeaconNodeFallback { Ok(val) => Ok(val), Err(e) => { debug!( - log, - "Request to beacon node failed"; - "node" => %candidate, - "error" => ?e, + node = %candidate, + error = ?e, + "Request to beacon node failed" ); inc_counter_vec(&ENDPOINT_ERRORS, &[candidate.as_ref()]); Err((candidate.to_string(), Error::RequestFailed(e))) @@ -666,11 +642,7 @@ impl BeaconNodeFallback { // Run `func` using a `candidate`, returning the value or capturing errors. for candidate in candidates.iter() { - futures.push(Self::run_on_candidate( - candidate.beacon_node.clone(), - &func, - &self.log, - )); + futures.push(Self::run_on_candidate(candidate.beacon_node.clone(), &func)); } drop(candidates); @@ -752,7 +724,6 @@ mod tests { use crate::beacon_node_health::BeaconNodeHealthTier; use eth2::SensitiveUrl; use eth2::Timeouts; - use logging::test_logger; use slot_clock::TestingSlotClock; use strum::VariantNames; use types::{BeaconBlockDeneb, MainnetEthSpec, Slot}; @@ -902,10 +873,9 @@ mod tests { candidates: Vec>, topics: Vec, spec: Arc, - log: Logger, ) -> BeaconNodeFallback { let mut beacon_node_fallback = - BeaconNodeFallback::new(candidates, Config::default(), topics, spec, log); + BeaconNodeFallback::new(candidates, Config::default(), topics, spec); beacon_node_fallback.set_slot_clock(TestingSlotClock::new( Slot::new(1), @@ -932,7 +902,6 @@ mod tests { ], vec![], spec.clone(), - test_logger(), ); // BeaconNodeHealthTier 1 @@ -979,7 +948,6 @@ mod tests { vec![beacon_node_1, beacon_node_2], vec![ApiTopic::Blocks], spec.clone(), - test_logger(), ); mock_beacon_node_1.mock_post_beacon_blinded_blocks_v2_ssz(Duration::from_secs(0)); @@ -1021,7 +989,6 @@ mod tests { vec![beacon_node_1, beacon_node_2, beacon_node_3], vec![], spec.clone(), - test_logger(), ); let mock1 = mock_beacon_node_1.mock_offline_node(); diff --git a/validator_client/doppelganger_service/Cargo.toml b/validator_client/doppelganger_service/Cargo.toml index 66b61a411b..803dd94322 100644 --- a/validator_client/doppelganger_service/Cargo.toml +++ b/validator_client/doppelganger_service/Cargo.toml @@ -8,11 +8,12 @@ authors = ["Sigma Prime "] beacon_node_fallback = { workspace = true } environment = { workspace = true } eth2 = { workspace = true } +logging = { workspace = true } parking_lot = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/validator_client/doppelganger_service/src/lib.rs b/validator_client/doppelganger_service/src/lib.rs index 4a593c2700..cb81b3ffc2 100644 --- a/validator_client/doppelganger_service/src/lib.rs +++ b/validator_client/doppelganger_service/src/lib.rs @@ -32,14 +32,15 @@ use beacon_node_fallback::BeaconNodeFallback; use environment::RuntimeContext; use eth2::types::LivenessResponseData; +use logging::crit; use parking_lot::RwLock; -use slog::{crit, error, info, Logger}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; use std::future::Future; use std::sync::Arc; use task_executor::ShutdownReason; use tokio::time::sleep; +use tracing::{error, info}; use types::{Epoch, EthSpec, PublicKeyBytes, Slot}; /// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator @@ -164,7 +165,6 @@ impl DoppelgangerState { /// doppelganger progression. async fn beacon_node_liveness( beacon_nodes: Arc>, - log: Logger, current_epoch: Epoch, validator_indices: Vec, ) -> LivenessResponses { @@ -203,10 +203,9 @@ async fn beacon_node_liveness( .await .unwrap_or_else(|e| { crit!( - log, - "Failed previous epoch liveness query"; - "error" => %e, - "previous_epoch" => %previous_epoch, + error = %e, + previous_epoch = %previous_epoch, + "Failed previous epoch liveness query" ); // Return an empty vec. In effect, this means to keep trying to make doppelganger // progress even if some of the calls are failing. @@ -239,10 +238,9 @@ async fn beacon_node_liveness( .await .unwrap_or_else(|e| { crit!( - log, - "Failed current epoch liveness query"; - "error" => %e, - "current_epoch" => %current_epoch, + error = %e, + current_epoch = %current_epoch, + "Failed current epoch liveness query" ); // Return an empty vec. In effect, this means to keep trying to make doppelganger // progress even if some of the calls are failing. @@ -257,11 +255,10 @@ async fn beacon_node_liveness( || current_epoch_responses.len() != previous_epoch_responses.len() { error!( - log, - "Liveness query omitted validators"; - "previous_epoch_response" => previous_epoch_responses.len(), - "current_epoch_response" => current_epoch_responses.len(), - "requested" => validator_indices.len(), + previous_epoch_response = previous_epoch_responses.len(), + current_epoch_response = current_epoch_responses.len(), + requested = validator_indices.len(), + "Liveness query omitted validators" ) } @@ -271,19 +268,12 @@ async fn beacon_node_liveness( } } +#[derive(Default)] pub struct DoppelgangerService { doppelganger_states: RwLock>, - log: Logger, } impl DoppelgangerService { - pub fn new(log: Logger) -> Self { - Self { - doppelganger_states: <_>::default(), - log, - } - } - /// Starts a reoccurring future which will try to keep the doppelganger service updated each /// slot. pub fn start_update_service( @@ -302,35 +292,25 @@ impl DoppelgangerService { let get_index = move |pubkey| validator_store.get_validator_index(&pubkey); // Define the `get_liveness` function as one that queries the beacon node API. - let log = service.log.clone(); let get_liveness = move |current_epoch, validator_indices| { - beacon_node_liveness( - beacon_nodes.clone(), - log.clone(), - current_epoch, - validator_indices, - ) + beacon_node_liveness(beacon_nodes.clone(), current_epoch, validator_indices) }; let mut shutdown_sender = context.executor.shutdown_sender(); - let log = service.log.clone(); + let mut shutdown_func = move || { if let Err(e) = shutdown_sender.try_send(ShutdownReason::Failure("Doppelganger detected.")) { crit!( - log, - "Failed to send shutdown signal"; - "msg" => "terminate this process immediately", - "error" => ?e + msg = "terminate this process immediately", + error = ?e, + "Failed to send shutdown signal" ); } }; - info!( - service.log, - "Doppelganger detection service started"; - ); + info!("Doppelganger detection service started"); context.executor.spawn( async move { @@ -360,9 +340,8 @@ impl DoppelgangerService { .await { error!( - service.log, - "Error during doppelganger detection"; - "error" => ?e + error = ?e, + "Error during doppelganger detection" ); } } @@ -387,10 +366,9 @@ impl DoppelgangerService { }) .unwrap_or_else(|| { crit!( - self.log, - "Validator unknown to doppelganger service"; - "msg" => "preventing validator from performing duties", - "pubkey" => ?validator + msg = "preventing validator from performing duties", + pubkey = ?validator, + "Validator unknown to doppelganger service" ); DoppelgangerStatus::UnknownToDoppelganger(validator) }) @@ -552,11 +530,7 @@ impl DoppelgangerService { // Resolve the index from the server response back to a public key. let Some(pubkey) = indices_map.get(&response.index) else { - crit!( - self.log, - "Inconsistent indices map"; - "validator_index" => response.index, - ); + crit!(validator_index = response.index, "Inconsistent indices map"); // Skip this result if an inconsistency is detected. continue; }; @@ -566,9 +540,8 @@ impl DoppelgangerService { state.next_check_epoch } else { crit!( - self.log, - "Inconsistent doppelganger state"; - "validator_pubkey" => ?pubkey, + validator_pubkey = ?pubkey, + "Inconsistent doppelganger state" ); // Skip this result if an inconsistency is detected. continue; @@ -582,15 +555,14 @@ impl DoppelgangerService { let violators_exist = !violators.is_empty(); if violators_exist { crit!( - self.log, - "Doppelganger(s) detected"; - "msg" => "A doppelganger occurs when two different validator clients run the \ - same public key. This validator client detected another instance of a local \ - validator on the network and is shutting down to prevent potential slashable \ - offences. Ensure that you are not running a duplicate or overlapping \ - validator client", - "doppelganger_indices" => ?violators - ) + msg = "A doppelganger occurs when two different validator clients run the \ + same public key. This validator client detected another instance of a local \ + validator on the network and is shutting down to prevent potential slashable \ + offences. Ensure that you are not running a duplicate or overlapping \ + validator client", + doppelganger_indices = ?violators, + "Doppelganger(s) detected" + ); } // The concept of "epoch satisfaction" is that for some epoch `e` we are *satisfied* that @@ -665,19 +637,17 @@ impl DoppelgangerService { doppelganger_state.complete_detection_in_epoch(previous_epoch); info!( - self.log, - "Found no doppelganger"; - "further_checks_remaining" => doppelganger_state.remaining_epochs, - "epoch" => response.epoch, - "validator_index" => response.index + further_checks_remaining = doppelganger_state.remaining_epochs, + epoch = %response.epoch, + validator_index = response.index, + "Found no doppelganger" ); if doppelganger_state.remaining_epochs == 0 { info!( - self.log, - "Doppelganger detection complete"; - "msg" => "starting validator", - "validator_index" => response.index + msg = "starting validator", + validator_index = response.index, + "Doppelganger detection complete" ); } } @@ -696,7 +666,6 @@ impl DoppelgangerService { mod test { use super::*; use futures::executor::block_on; - use logging::test_logger; use slot_clock::TestingSlotClock; use std::future; use std::time::Duration; @@ -740,13 +709,12 @@ mod test { fn build(self) -> TestScenario { let mut rng = XorShiftRng::from_seed([42; 16]); let slot_clock = TestingSlotClock::new(Slot::new(0), GENESIS_TIME, SLOT_DURATION); - let log = test_logger(); TestScenario { validators: (0..self.validator_count) .map(|_| PublicKeyBytes::random_for_test(&mut rng)) .collect(), - doppelganger: DoppelgangerService::new(log), + doppelganger: DoppelgangerService::default(), slot_clock, } } diff --git a/validator_client/graffiti_file/Cargo.toml b/validator_client/graffiti_file/Cargo.toml index 8868f5aec8..b3bbeb1fd7 100644 --- a/validator_client/graffiti_file/Cargo.toml +++ b/validator_client/graffiti_file/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [dependencies] bls = { workspace = true } serde = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/validator_client/graffiti_file/src/lib.rs b/validator_client/graffiti_file/src/lib.rs index 9dab2e7827..86f582aa38 100644 --- a/validator_client/graffiti_file/src/lib.rs +++ b/validator_client/graffiti_file/src/lib.rs @@ -1,12 +1,11 @@ +use bls::PublicKeyBytes; use serde::{Deserialize, Serialize}; -use slog::warn; use std::collections::HashMap; use std::fs::File; use std::io::{prelude::*, BufReader}; use std::path::PathBuf; use std::str::FromStr; - -use bls::PublicKeyBytes; +use tracing::warn; use types::{graffiti::GraffitiString, Graffiti}; #[derive(Debug)] @@ -108,7 +107,6 @@ fn read_line(line: &str) -> Result<(Option, Graffiti), Error> { // the next block produced by the validator with the given public key. pub fn determine_graffiti( validator_pubkey: &PublicKeyBytes, - log: &slog::Logger, graffiti_file: Option, validator_definition_graffiti: Option, graffiti_flag: Option, @@ -117,7 +115,7 @@ pub fn determine_graffiti( .and_then(|mut g| match g.load_graffiti(validator_pubkey) { Ok(g) => g, Err(e) => { - warn!(log, "Failed to read graffiti file"; "error" => ?e); + warn!(error = ?e, "Failed to read graffiti file"); None } }) diff --git a/validator_client/http_api/Cargo.toml b/validator_client/http_api/Cargo.toml index 651e658a7a..482212d890 100644 --- a/validator_client/http_api/Cargo.toml +++ b/validator_client/http_api/Cargo.toml @@ -29,9 +29,9 @@ parking_lot = { workspace = true } rand = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } signing_method = { workspace = true } slashing_protection = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } sysinfo = { workspace = true } system_health = { workspace = true } @@ -39,6 +39,7 @@ task_executor = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } types = { workspace = true } url = { workspace = true } validator_dir = { workspace = true } diff --git a/validator_client/http_api/src/create_signed_voluntary_exit.rs b/validator_client/http_api/src/create_signed_voluntary_exit.rs index 32269b202b..7a9dc798d6 100644 --- a/validator_client/http_api/src/create_signed_voluntary_exit.rs +++ b/validator_client/http_api/src/create_signed_voluntary_exit.rs @@ -1,8 +1,8 @@ use bls::{PublicKey, PublicKeyBytes}; use eth2::types::GenericResponse; -use slog::{info, Logger}; use slot_clock::SlotClock; use std::sync::Arc; +use tracing::info; use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; use validator_store::ValidatorStore; @@ -11,7 +11,6 @@ pub async fn create_signed_voluntary_exit, validator_store: Arc>, slot_clock: T, - log: Logger, ) -> Result, warp::Rejection> { let epoch = match maybe_epoch { Some(epoch) => epoch, @@ -45,10 +44,9 @@ pub async fn create_signed_voluntary_exit pubkey_bytes.as_hex_string(), - "epoch" => epoch + validator = pubkey_bytes.as_hex_string(), + %epoch, + "Signing voluntary exit" ); let signed_voluntary_exit = validator_store diff --git a/validator_client/http_api/src/keystores.rs b/validator_client/http_api/src/keystores.rs index fd6b4fdae5..c2bcfe5ab4 100644 --- a/validator_client/http_api/src/keystores.rs +++ b/validator_client/http_api/src/keystores.rs @@ -11,12 +11,12 @@ use eth2::lighthouse_vc::{ use eth2_keystore::Keystore; use initialized_validators::{Error, InitializedValidators}; use signing_method::SigningMethod; -use slog::{info, warn, Logger}; use slot_clock::SlotClock; use std::path::PathBuf; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::runtime::Handle; +use tracing::{info, warn}; use types::{EthSpec, PublicKeyBytes}; use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder}; use validator_store::ValidatorStore; @@ -64,7 +64,6 @@ pub fn import( secrets_dir: Option, validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { // Check request validity. This is the only cases in which we should return a 4xx code. if request.keystores.len() != request.passwords.len() { @@ -88,18 +87,14 @@ pub fn import( .iter() .any(|data| data.pubkey == pubkey_bytes) { - warn!( - log, - "Slashing protection data not provided"; - "public_key" => ?public_key, - ); + warn!(?public_key, "Slashing protection data not provided"); } } } validator_store.import_slashing_protection(slashing_protection) } else { - warn!(log, "No slashing protection data provided with keystores"); + warn!("No slashing protection data provided with keystores"); Ok(()) }; @@ -133,10 +128,9 @@ pub fn import( Ok(status) => Status::ok(status), Err(e) => { warn!( - log, - "Error importing keystore, skipped"; - "pubkey" => pubkey_str, - "error" => ?e, + pubkey = pubkey_str, + error = ?e, + "Error importing keystore, skipped" ); Status::error(ImportKeystoreStatus::Error, e) } @@ -157,9 +151,8 @@ pub fn import( if successful_import > 0 { info!( - log, - "Imported keystores via standard HTTP API"; - "count" => successful_import, + count = successful_import, + "Imported keystores via standard HTTP API" ); } @@ -243,9 +236,8 @@ pub fn delete( request: DeleteKeystoresRequest, validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { - let export_response = export(request, validator_store, task_executor, log.clone())?; + let export_response = export(request, validator_store, task_executor)?; // Check the status is Deleted to confirm deletion is successful, then only display the log let successful_deletion = export_response @@ -256,9 +248,8 @@ pub fn delete( if successful_deletion > 0 { info!( - log, - "Deleted keystore via standard HTTP API"; - "count" => successful_deletion, + count = successful_deletion, + "Deleted keystore via standard HTTP API" ); } @@ -276,7 +267,6 @@ pub fn export( request: DeleteKeystoresRequest, validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { // Remove from initialized validators. let initialized_validators_rwlock = validator_store.initialized_validators(); @@ -294,10 +284,9 @@ pub fn export( Ok(status) => status, Err(error) => { warn!( - log, - "Error deleting keystore"; - "pubkey" => ?pubkey_bytes, - "error" => ?error, + pubkey = ?pubkey_bytes, + ?error, + "Error deleting keystore" ); SingleExportKeystoresResponse { status: Status::error(DeleteKeystoreStatus::Error, error), diff --git a/validator_client/http_api/src/lib.rs b/validator_client/http_api/src/lib.rs index 9c3e3da63d..5bb4747bfe 100644 --- a/validator_client/http_api/src/lib.rs +++ b/validator_client/http_api/src/lib.rs @@ -34,10 +34,10 @@ use eth2::lighthouse_vc::{ }; use health_metrics::observe::Observe; use lighthouse_version::version_with_platform; +use logging::crit; use logging::SSELoggingComponents; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use slog::{crit, info, warn, Logger}; use slot_clock::SlotClock; use std::collections::HashMap; use std::future::Future; @@ -49,6 +49,7 @@ use sysinfo::{System, SystemExt}; use system_health::observe_system_health_vc; use task_executor::TaskExecutor; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; +use tracing::{info, warn}; use types::{ChainSpec, ConfigAndPreset, EthSpec}; use validator_dir::Builder as ValidatorDirBuilder; use validator_services::block_service::BlockService; @@ -87,7 +88,6 @@ pub struct Context { pub graffiti_flag: Option, pub spec: Arc, pub config: Config, - pub log: Logger, pub sse_logging_components: Option, pub slot_clock: T, pub _phantom: PhantomData, @@ -148,7 +148,6 @@ pub fn serve( let config = &ctx.config; let allow_keystore_export = config.allow_keystore_export; let store_passwords_in_secrets_dir = config.store_passwords_in_secrets_dir; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -165,7 +164,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -179,9 +178,8 @@ pub fn serve( Ok(abs_path) => api_token_path = abs_path, Err(e) => { warn!( - log, - "Error canonicalizing token path"; - "error" => ?e, + error = ?e, + "Error canonicalizing token path" ); } }; @@ -239,9 +237,6 @@ pub fn serve( let inner_graffiti_flag = ctx.graffiti_flag; let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag); - let inner_ctx = ctx.clone(); - let log_filter = warp::any().map(move || inner_ctx.log.clone()); - let inner_slot_clock = ctx.slot_clock.clone(); let slot_clock_filter = warp::any().map(move || inner_slot_clock.clone()); @@ -399,12 +394,10 @@ pub fn serve( .and(validator_store_filter.clone()) .and(graffiti_file_filter.clone()) .and(graffiti_flag_filter) - .and(log_filter.clone()) .then( |validator_store: Arc>, graffiti_file: Option, - graffiti_flag: Option, - log| { + graffiti_flag: Option| { blocking_json_task(move || { let mut result = HashMap::new(); for (key, graffiti_definition) in validator_store @@ -414,7 +407,6 @@ pub fn serve( { let graffiti = determine_graffiti( key, - &log, graffiti_file.clone(), graffiti_definition, graffiti_flag, @@ -767,7 +759,7 @@ pub fn serve( // Disabling an already disabled validator *with no other changes* is a // no-op. (Some(false), None) - if body.enabled.map_or(true, |enabled| !enabled) + if body.enabled.is_none_or(|enabled| !enabled) && body.gas_limit.is_none() && body.builder_boost_factor.is_none() && body.builder_proposals.is_none() @@ -834,11 +826,10 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(move |request, validator_store, task_executor, log| { + .then(move |request, validator_store, task_executor| { blocking_json_task(move || { if allow_keystore_export { - keystores::export(request, validator_store, task_executor, log) + keystores::export(request, validator_store, task_executor) } else { Err(warp_utils::reject::custom_bad_request( "keystore export is disabled".to_string(), @@ -1079,14 +1070,12 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .and(slot_clock_filter) - .and(log_filter.clone()) .and(task_executor_filter.clone()) .then( |pubkey: PublicKey, query: api_types::VoluntaryExitQuery, validator_store: Arc>, slot_clock: T, - log, task_executor: TaskExecutor| { blocking_json_task(move || { if let Some(handle) = task_executor.handle() { @@ -1096,7 +1085,6 @@ pub fn serve( query.epoch, validator_store, slot_clock, - log, ))?; Ok(signed_voluntary_exit) } else { @@ -1196,9 +1184,8 @@ pub fn serve( .and(secrets_dir_filter) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) .then( - move |request, validator_dir, secrets_dir, validator_store, task_executor, log| { + move |request, validator_dir, secrets_dir, validator_store, task_executor| { let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir); blocking_json_task(move || { keystores::import( @@ -1207,7 +1194,6 @@ pub fn serve( secrets_dir, validator_store, task_executor, - log, ) }) }, @@ -1218,11 +1204,8 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - keystores::delete(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || keystores::delete(request, validator_store, task_executor)) }); // GET /eth/v1/remotekeys @@ -1237,11 +1220,8 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - remotekeys::import(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || remotekeys::import(request, validator_store, task_executor)) }); // DELETE /eth/v1/remotekeys @@ -1249,11 +1229,8 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter) .and(task_executor_filter) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - remotekeys::delete(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || remotekeys::delete(request, validator_store, task_executor)) }); // Subscribe to get VC logs via Server side events @@ -1271,7 +1248,9 @@ pub fn serve( match msg { Ok(data) => { // Serialize to json - match data.to_json_string() { + match serde_json::to_string(&data) + .map_err(|e| format!("{:?}", e)) + { // Send the json as a Server Sent Event Ok(json) => Event::default().json_data(json).map_err(|e| { warp_utils::reject::server_sent_event_error(format!( @@ -1364,10 +1343,9 @@ pub fn serve( )?; info!( - log, - "HTTP API started"; - "listen_address" => listening_socket.to_string(), - "api_token_file" => ?api_token_path, + listen_address = listening_socket.to_string(), + ?api_token_path, + "HTTP API started" ); Ok((listening_socket, server)) diff --git a/validator_client/http_api/src/remotekeys.rs b/validator_client/http_api/src/remotekeys.rs index 289be57182..49d666f303 100644 --- a/validator_client/http_api/src/remotekeys.rs +++ b/validator_client/http_api/src/remotekeys.rs @@ -8,11 +8,11 @@ use eth2::lighthouse_vc::std_types::{ ListRemotekeysResponse, SingleListRemotekeysResponse, Status, }; use initialized_validators::{Error, InitializedValidators}; -use slog::{info, warn, Logger}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::runtime::Handle; +use tracing::{info, warn}; use types::{EthSpec, PublicKeyBytes}; use url::Url; use validator_store::ValidatorStore; @@ -52,12 +52,10 @@ pub fn import( request: ImportRemotekeysRequest, validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { info!( - log, - "Importing remotekeys via standard HTTP API"; - "count" => request.remote_keys.len(), + count = request.remote_keys.len(), + "Importing remotekeys via standard HTTP API" ); // Import each remotekey. Some remotekeys may fail to be imported, so we record a status for each. let mut statuses = Vec::with_capacity(request.remote_keys.len()); @@ -70,10 +68,9 @@ pub fn import( Ok(status) => Status::ok(status), Err(e) => { warn!( - log, - "Error importing keystore, skipped"; - "pubkey" => remotekey.pubkey.to_string(), - "error" => ?e, + pubkey = remotekey.pubkey.to_string(), + error = ?e, + "Error importing keystore, skipped" ); Status::error(ImportRemotekeyStatus::Error, e) } @@ -148,12 +145,10 @@ pub fn delete( request: DeleteRemotekeysRequest, validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { info!( - log, - "Deleting remotekeys via standard HTTP API"; - "count" => request.pubkeys.len(), + count = request.pubkeys.len(), + "Deleting remotekeys via standard HTTP API" ); // Remove from initialized validators. let initialized_validators_rwlock = validator_store.initialized_validators(); @@ -171,10 +166,9 @@ pub fn delete( Ok(status) => Status::ok(status), Err(error) => { warn!( - log, - "Error deleting keystore"; - "pubkey" => ?pubkey_bytes, - "error" => ?error, + pubkey = ?pubkey_bytes, + ?error, + "Error deleting keystore" ); Status::error(DeleteRemotekeyStatus::Error, error) } diff --git a/validator_client/http_api/src/test_utils.rs b/validator_client/http_api/src/test_utils.rs index 0531626846..4a5d3b6cc7 100644 --- a/validator_client/http_api/src/test_utils.rs +++ b/validator_client/http_api/src/test_utils.rs @@ -14,7 +14,6 @@ use eth2::{ use eth2_keystore::KeystoreBuilder; use initialized_validators::key_cache::{KeyCache, CACHE_FILENAME}; use initialized_validators::{InitializedValidators, OnDecryptFailure}; -use logging::test_logger; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME}; @@ -70,8 +69,6 @@ impl ApiTester { } pub async fn new_with_http_config(http_config: HttpConfig) -> Self { - let log = test_logger(); - let validator_dir = tempdir().unwrap(); let secrets_dir = tempdir().unwrap(); let token_path = tempdir().unwrap().path().join(PK_FILENAME); @@ -82,7 +79,6 @@ impl ApiTester { validator_defs, validator_dir.path().into(), Default::default(), - log.clone(), ) .await .unwrap(); @@ -110,11 +106,10 @@ impl ApiTester { slashing_protection, Hash256::repeat_byte(42), spec.clone(), - Some(Arc::new(DoppelgangerService::new(log.clone()))), + Some(Arc::new(DoppelgangerService::default())), slot_clock.clone(), &config, test_runtime.task_executor.clone(), - log.clone(), )); validator_store @@ -134,7 +129,6 @@ impl ApiTester { graffiti_flag: Some(Graffiti::default()), spec, config: http_config, - log, sse_logging_components: None, slot_clock, _phantom: PhantomData, diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index 4e9acc4237..5468718fb5 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -18,7 +18,6 @@ use eth2::{ Error as ApiError, }; use eth2_keystore::KeystoreBuilder; -use logging::test_logger; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME}; @@ -61,8 +60,6 @@ impl ApiTester { } pub async fn new_with_config(config: ValidatorStoreConfig) -> Self { - let log = test_logger(); - let validator_dir = tempdir().unwrap(); let secrets_dir = tempdir().unwrap(); let token_path = tempdir().unwrap().path().join("api-token.txt"); @@ -73,7 +70,6 @@ impl ApiTester { validator_defs, validator_dir.path().into(), InitializedValidatorsConfig::default(), - log.clone(), ) .await .unwrap(); @@ -100,11 +96,10 @@ impl ApiTester { slashing_protection, Hash256::repeat_byte(42), spec.clone(), - Some(Arc::new(DoppelgangerService::new(log.clone()))), + Some(Arc::new(DoppelgangerService::default())), slot_clock.clone(), &config, test_runtime.task_executor.clone(), - log.clone(), )); validator_store @@ -133,7 +128,6 @@ impl ApiTester { http_token_path: token_path, }, sse_logging_components: None, - log, slot_clock: slot_clock.clone(), _phantom: PhantomData, }); diff --git a/validator_client/http_metrics/Cargo.toml b/validator_client/http_metrics/Cargo.toml index a3432410bc..f2684da4b1 100644 --- a/validator_client/http_metrics/Cargo.toml +++ b/validator_client/http_metrics/Cargo.toml @@ -7,12 +7,13 @@ authors = ["Sigma Prime "] [dependencies] health_metrics = { workspace = true } lighthouse_version = { workspace = true } +logging = { workspace = true } malloc_utils = { workspace = true } metrics = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_metrics = { workspace = true } validator_services = { workspace = true } diff --git a/validator_client/http_metrics/src/lib.rs b/validator_client/http_metrics/src/lib.rs index f1c6d4ed8a..6bf18e7b93 100644 --- a/validator_client/http_metrics/src/lib.rs +++ b/validator_client/http_metrics/src/lib.rs @@ -3,15 +3,16 @@ //! For other endpoints, see the `http_api` crate. use lighthouse_version::version_with_platform; +use logging::crit; use malloc_utils::scrape_allocator_metrics; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use slog::{crit, info, Logger}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::info; use types::EthSpec; use validator_services::duties_service::DutiesService; use validator_store::ValidatorStore; @@ -48,7 +49,6 @@ pub struct Shared { pub struct Context { pub config: Config, pub shared: RwLock>, - pub log: Logger, } /// Configuration for the HTTP server. @@ -93,7 +93,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -110,7 +109,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -151,9 +150,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/validator_client/initialized_validators/Cargo.toml b/validator_client/initialized_validators/Cargo.toml index 05e85261f9..8b2ae62aea 100644 --- a/validator_client/initialized_validators/Cargo.toml +++ b/validator_client/initialized_validators/Cargo.toml @@ -18,8 +18,8 @@ reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } signing_method = { workspace = true } -slog = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } url = { workspace = true } validator_dir = { workspace = true } diff --git a/validator_client/initialized_validators/src/lib.rs b/validator_client/initialized_validators/src/lib.rs index bd64091dae..cbc1287a85 100644 --- a/validator_client/initialized_validators/src/lib.rs +++ b/validator_client/initialized_validators/src/lib.rs @@ -22,13 +22,13 @@ use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; use reqwest::{Certificate, Client, Error as ReqwestError, Identity}; use serde::{Deserialize, Serialize}; use signing_method::SigningMethod; -use slog::{debug, error, info, warn, Logger}; use std::collections::{HashMap, HashSet}; use std::fs::{self, File}; use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, error, info, warn}; use types::graffiti::GraffitiString; use types::{Address, Graffiti, Keypair, PublicKey, PublicKeyBytes}; use url::{ParseError, Url}; @@ -503,8 +503,6 @@ pub struct InitializedValidators { validators: HashMap, /// The clients used for communications with a remote signer. web3_signer_client_map: Option>, - /// For logging via `slog`. - log: Logger, config: Config, } @@ -514,7 +512,6 @@ impl InitializedValidators { definitions: ValidatorDefinitions, validators_dir: PathBuf, config: Config, - log: Logger, ) -> Result { let mut this = Self { validators_dir, @@ -522,7 +519,6 @@ impl InitializedValidators { validators: HashMap::default(), web3_signer_client_map: None, config, - log, }; this.update_validators().await?; Ok(this) @@ -1151,10 +1147,9 @@ impl InitializedValidators { for uuid in cache.uuids() { if !definitions_map.contains_key(uuid) { debug!( - self.log, - "Resetting the key cache"; - "keystore_uuid" => %uuid, - "reason" => "impossible to decrypt due to missing keystore", + keystore_uuid = %uuid, + reason = "impossible to decrypt due to missing keystore", + "Resetting the key cache" ); return Ok(KeyCache::new()); } @@ -1281,30 +1276,27 @@ impl InitializedValidators { self.validators .insert(init.voting_public_key().compress(), init); info!( - self.log, - "Enabled validator"; - "signing_method" => "local_keystore", - "voting_pubkey" => format!("{:?}", def.voting_public_key), + signing_method = "local_keystore", + voting_pubkey = format!("{:?}", def.voting_public_key), + "Enabled validator" ); if let Some(lockfile_path) = existing_lockfile_path { warn!( - self.log, - "Ignored stale lockfile"; - "path" => lockfile_path.display(), - "cause" => "Ungraceful shutdown (harmless) OR \ + path = ?lockfile_path.display(), + cause = "Ungraceful shutdown (harmless) OR \ non-Lighthouse client using this keystore \ - (risky)" + (risky)", + "Ignored stale lockfile" ); } } Err(e) => { error!( - self.log, - "Failed to initialize validator"; - "error" => format!("{:?}", e), - "signing_method" => "local_keystore", - "validator" => format!("{:?}", def.voting_public_key) + error = format!("{:?}", e), + signing_method = "local_keystore", + validator = format!("{:?}", def.voting_public_key), + "Failed to initialize validator" ); // Exit on an invalid validator. @@ -1327,19 +1319,17 @@ impl InitializedValidators { .insert(init.voting_public_key().compress(), init); info!( - self.log, - "Enabled validator"; - "signing_method" => "remote_signer", - "voting_pubkey" => format!("{:?}", def.voting_public_key), + signing_method = "remote_signer", + voting_pubkey = format!("{:?}", def.voting_public_key), + "Enabled validator" ); } Err(e) => { error!( - self.log, - "Failed to initialize validator"; - "error" => format!("{:?}", e), - "signing_method" => "remote_signer", - "validator" => format!("{:?}", def.voting_public_key) + error = format!("{:?}", e), + signing_method = "remote_signer", + validator = format!("{:?}", def.voting_public_key), + "Failed to initialize validator" ); // Exit on an invalid validator. @@ -1364,9 +1354,8 @@ impl InitializedValidators { } info!( - self.log, - "Disabled validator"; - "voting_pubkey" => format!("{:?}", def.voting_public_key) + voting_pubkey = format!("{:?}", def.voting_public_key), + "Disabled validator" ); } } @@ -1378,23 +1367,18 @@ impl InitializedValidators { } let validators_dir = self.validators_dir.clone(); - let log = self.log.clone(); if has_local_definitions && key_cache.is_modified() { tokio::task::spawn_blocking(move || { match key_cache.save(validators_dir) { - Err(e) => warn!( - log, - "Error during saving of key_cache"; - "err" => format!("{:?}", e) - ), - Ok(true) => info!(log, "Modified key_cache saved successfully"), + Err(e) => warn!(err = format!("{:?}", e), "Error during saving of key_cache"), + Ok(true) => info!("Modified key_cache saved successfully"), _ => {} }; }) .await .map_err(Error::TokioJoin)?; } else { - debug!(log, "Key cache not modified"); + debug!("Key cache not modified"); } // Update the enabled and total validator counts diff --git a/validator_client/slashing_protection/Cargo.toml b/validator_client/slashing_protection/Cargo.toml index 1a098742d8..88e6dd794d 100644 --- a/validator_client/slashing_protection/Cargo.toml +++ b/validator_client/slashing_protection/Cargo.toml @@ -19,6 +19,7 @@ rusqlite = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index 71611339f9..f4c844d314 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -1113,7 +1113,7 @@ fn max_or(opt_x: Option, y: T) -> T { /// /// If prev is `None` and `new` is `Some` then `true` is returned. fn monotonic(new: Option, prev: Option) -> bool { - new.is_some_and(|new_val| prev.map_or(true, |prev_val| new_val >= prev_val)) + new.is_some_and(|new_val| prev.is_none_or(|prev_val| new_val >= prev_val)) } /// The result of importing a single entry from an interchange file. diff --git a/validator_client/src/check_synced.rs b/validator_client/src/check_synced.rs new file mode 100644 index 0000000000..5f3e0fe036 --- /dev/null +++ b/validator_client/src/check_synced.rs @@ -0,0 +1,25 @@ +use crate::beacon_node_fallback::CandidateError; +use eth2::{types::Slot, BeaconNodeHttpClient}; +use tracing::warn; + +pub async fn check_node_health( + beacon_node: &BeaconNodeHttpClient, +) -> Result<(Slot, bool, bool), CandidateError> { + let resp = match beacon_node.get_node_syncing().await { + Ok(resp) => resp, + Err(e) => { + warn!( + error = %e, + "Unable connect to beacon node" + ); + + return Err(CandidateError::Offline); + } + }; + + Ok(( + resp.data.head_slot, + resp.data.is_optimistic, + resp.data.el_offline, + )) +} diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index dfcd2064e5..18bd736957 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -97,6 +97,15 @@ pub struct ValidatorClient { )] pub disable_auto_discover: bool, + #[clap( + long, + help = "Disable the performance of attestation duties (and sync committee duties). This \ + flag should only be used in emergencies to prioritise block proposal duties.", + display_order = 0, + help_heading = FLAG_HEADER + )] + pub disable_attesting: bool, + #[clap( long, help = "If present, the validator client will use longer timeouts for requests \ @@ -107,6 +116,20 @@ pub struct ValidatorClient { )] pub use_long_timeouts: bool, + #[clap( + long, + requires = "use_long_timeouts", + default_value_t = 1, + help = "If present, the validator client will use a multiplier for the timeout \ + when making requests to the beacon node. This only takes effect when \ + the `--use-long-timeouts` flag is present. The timeouts will be the slot \ + duration multiplied by this value. This flag is generally not recommended, \ + longer timeouts can cause missed duties when fallbacks are used.", + display_order = 0, + help_heading = FLAG_HEADER, + )] + pub long_timeouts_multiplier: u32, + #[clap( long, value_name = "CERTIFICATE-FILES", diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 2a848e2022..cfc88969c9 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -12,11 +12,11 @@ use graffiti_file::GraffitiFile; use initialized_validators::Config as InitializedValidatorsConfig; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{info, warn, Logger}; use std::fs; use std::net::IpAddr; use std::path::PathBuf; use std::time::Duration; +use tracing::{info, warn}; use types::GRAFFITI_BYTES_LEN; use validator_http_api::{self, PK_FILENAME}; use validator_http_metrics; @@ -49,6 +49,8 @@ pub struct Config { pub init_slashing_protection: bool, /// If true, use longer timeouts for requests made to the beacon node. pub use_long_timeouts: bool, + /// Multiplier to use for long timeouts. + pub long_timeouts_multiplier: u32, /// Graffiti to be inserted everytime we create a block. pub graffiti: Option, /// Graffiti file to load per validator graffitis. @@ -85,6 +87,7 @@ pub struct Config { /// Configuration for the initialized validators #[serde(flatten)] pub initialized_validators: InitializedValidatorsConfig, + pub disable_attesting: bool, } impl Default for Config { @@ -111,6 +114,7 @@ impl Default for Config { disable_auto_discover: false, init_slashing_protection: false, use_long_timeouts: false, + long_timeouts_multiplier: 1, graffiti: None, graffiti_file: None, http_api: <_>::default(), @@ -126,6 +130,7 @@ impl Default for Config { validator_registration_batch_size: 500, distributed: false, initialized_validators: <_>::default(), + disable_attesting: false, } } } @@ -136,7 +141,6 @@ impl Config { pub fn from_cli( cli_args: &ArgMatches, validator_client_config: &ValidatorClient, - log: &Logger, ) -> Result { let mut config = Config::default(); @@ -194,6 +198,7 @@ impl Config { config.disable_auto_discover = validator_client_config.disable_auto_discover; config.init_slashing_protection = validator_client_config.init_slashing_protection; config.use_long_timeouts = validator_client_config.use_long_timeouts; + config.long_timeouts_multiplier = validator_client_config.long_timeouts_multiplier; if let Some(graffiti_file_path) = validator_client_config.graffiti_file.as_ref() { let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into()); @@ -201,7 +206,10 @@ impl Config { .read_graffiti_file() .map_err(|e| format!("Error reading graffiti file: {:?}", e))?; config.graffiti_file = Some(graffiti_file); - info!(log, "Successfully loaded graffiti file"; "path" => graffiti_file_path.to_str()); + info!( + path = graffiti_file_path.to_str(), + "Successfully loaded graffiti file" + ); } if let Some(input_graffiti) = validator_client_config.graffiti.as_ref() { @@ -369,16 +377,17 @@ impl Config { config.validator_store.enable_web3signer_slashing_protection = if validator_client_config.disable_slashing_protection_web3signer { warn!( - log, - "Slashing protection for remote keys disabled"; - "info" => "ensure slashing protection on web3signer is enabled or you WILL \ - get slashed" + info = "ensure slashing protection on web3signer is enabled or you WILL \ + get slashed", + "Slashing protection for remote keys disabled" ); false } else { true }; + config.disable_attesting = validator_client_config.disable_attesting; + Ok(config) } } diff --git a/validator_client/src/latency.rs b/validator_client/src/latency.rs index 22f02c7c0b..edd8daa731 100644 --- a/validator_client/src/latency.rs +++ b/validator_client/src/latency.rs @@ -1,9 +1,9 @@ use beacon_node_fallback::BeaconNodeFallback; use environment::RuntimeContext; -use slog::debug; use slot_clock::SlotClock; use std::sync::Arc; use tokio::time::sleep; +use tracing::debug; use types::EthSpec; /// The latency service will run 11/12ths of the way through the slot. @@ -17,8 +17,6 @@ pub fn start_latency_service( slot_clock: T, beacon_nodes: Arc>, ) { - let log = context.log().clone(); - let future = async move { loop { let sleep_time = slot_clock @@ -39,10 +37,9 @@ pub fn start_latency_service( for (i, measurement) in beacon_nodes.measure_latency().await.iter().enumerate() { if let Some(latency) = measurement.latency { debug!( - log, - "Measured BN latency"; - "node" => &measurement.beacon_node_id, - "latency" => latency.as_millis(), + node = &measurement.beacon_node_id, + latency = latency.as_millis(), + "Measured BN latency" ); validator_metrics::observe_timer_vec( &validator_metrics::VC_BEACON_NODE_LATENCY, diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 2385ebc19a..1198586840 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -23,7 +23,6 @@ use initialized_validators::Error::UnableToOpenVotingKeystore; use notifier::spawn_notifier; use parking_lot::RwLock; use reqwest::Certificate; -use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use slot_clock::SystemTimeSlotClock; use std::fs::File; @@ -37,6 +36,7 @@ use tokio::{ sync::mpsc, time::{sleep, Duration}, }; +use tracing::{debug, error, info, warn}; use types::{EthSpec, Hash256}; use validator_http_api::ApiSecret; use validator_services::{ @@ -99,7 +99,7 @@ impl ProductionValidatorClient { cli_args: &ArgMatches, validator_client_config: &ValidatorClient, ) -> Result { - let config = Config::from_cli(cli_args, validator_client_config, context.log()) + let config = Config::from_cli(cli_args, validator_client_config) .map_err(|e| format!("Unable to initialize config: {}", e))?; Self::new(context, config).await } @@ -107,8 +107,6 @@ impl ProductionValidatorClient { /// Instantiates the validator client, _without_ starting the timers to trigger block /// and attestation production. pub async fn new(context: RuntimeContext, config: Config) -> Result { - let log = context.log().clone(); - // Attempt to raise soft fd limit. The behavior is OS specific: // `linux` - raise soft fd limit to hard // `macos` - raise soft fd limit to `min(kernel limit, hard fd limit)` @@ -116,25 +114,20 @@ impl ProductionValidatorClient { match fdlimit::raise_fd_limit().map_err(|e| format!("Unable to raise fd limit: {}", e))? { fdlimit::Outcome::LimitRaised { from, to } => { debug!( - log, - "Raised soft open file descriptor resource limit"; - "old_limit" => from, - "new_limit" => to + old_limit = from, + new_limit = to, + "Raised soft open file descriptor resource limit" ); } fdlimit::Outcome::Unsupported => { - debug!( - log, - "Raising soft open file descriptor resource limit is not supported" - ); + debug!("Raising soft open file descriptor resource limit is not supported"); } }; info!( - log, - "Starting validator client"; - "beacon_nodes" => format!("{:?}", &config.beacon_nodes), - "validator_dir" => format!("{:?}", config.validator_dir), + beacon_nodes = ?config.beacon_nodes, + validator_dir = ?config.validator_dir, + "Starting validator client" ); // Optionally start the metrics server. @@ -149,7 +142,6 @@ impl ProductionValidatorClient { Arc::new(validator_http_metrics::Context { config: config.http_metrics.clone(), shared: RwLock::new(shared), - log: log.clone(), }); let exit = context.executor.exit(); @@ -164,15 +156,14 @@ impl ProductionValidatorClient { Some(ctx) } else { - info!(log, "HTTP metrics server is disabled"); + info!("HTTP metrics server is disabled"); None }; // Start the explorer client which periodically sends validator process // and system metrics to the configured endpoint. if let Some(monitoring_config) = &config.monitoring_api { - let monitoring_client = - MonitoringHttpClient::new(monitoring_config, context.log().clone())?; + let monitoring_client = MonitoringHttpClient::new(monitoring_config)?; monitoring_client.auto_update( context.executor.clone(), vec![ProcessType::Validator, ProcessType::System], @@ -184,7 +175,7 @@ impl ProductionValidatorClient { if !config.disable_auto_discover { let new_validators = validator_defs - .discover_local_keystores(&config.validator_dir, &config.secrets_dir, &log) + .discover_local_keystores(&config.validator_dir, &config.secrets_dir) .map_err(|e| format!("Unable to discover local validator keystores: {:?}", e))?; validator_defs.save(&config.validator_dir).map_err(|e| { format!( @@ -192,18 +183,13 @@ impl ProductionValidatorClient { e ) })?; - info!( - log, - "Completed validator discovery"; - "new_validators" => new_validators, - ); + info!(new_validators, "Completed validator discovery"); } let validators = InitializedValidators::from_definitions( validator_defs, config.validator_dir.clone(), config.initialized_validators.clone(), - log.clone(), ) .await .map_err(|e| { @@ -220,17 +206,17 @@ impl ProductionValidatorClient { let voting_pubkeys: Vec<_> = validators.iter_voting_pubkeys().collect(); info!( - log, - "Initialized validators"; - "disabled" => validators.num_total().saturating_sub(validators.num_enabled()), - "enabled" => validators.num_enabled(), + disabled = validators + .num_total() + .saturating_sub(validators.num_enabled()), + enabled = validators.num_enabled(), + "Initialized validators" ); if voting_pubkeys.is_empty() { warn!( - log, - "No enabled validators"; - "hint" => "create validators via the API, or the `lighthouse account` CLI command" + hint = "create validators via the API, or the `lighthouse account` CLI command", + "No enabled validators" ); } @@ -305,10 +291,7 @@ impl ProductionValidatorClient { // Use quicker timeouts if a fallback beacon node exists. let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts { - info!( - log, - "Fallback endpoints are available, using optimized timeouts."; - ); + info!("Fallback endpoints are available, using optimized timeouts."); Timeouts { attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT, attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT, @@ -330,7 +313,7 @@ impl ProductionValidatorClient { get_validator_block: slot_duration / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT, } } else { - Timeouts::set_all(slot_duration) + Timeouts::set_all(slot_duration.saturating_mul(config.long_timeouts_multiplier)) }; Ok(BeaconNodeHttpClient::from_components( @@ -394,7 +377,6 @@ impl ProductionValidatorClient { config.beacon_node_fallback, config.broadcast_topics.clone(), context.eth2_config.spec.clone(), - log.clone(), ); let mut proposer_nodes: BeaconNodeFallback<_, E> = BeaconNodeFallback::new( @@ -402,12 +384,11 @@ impl ProductionValidatorClient { config.beacon_node_fallback, config.broadcast_topics.clone(), context.eth2_config.spec.clone(), - log.clone(), ); // Perform some potentially long-running initialization tasks. let (genesis_time, genesis_validators_root) = tokio::select! { - tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes, &context) => tuple?, + tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes) => tuple?, () = context.executor.exit() => return Err("Shutting down".to_string()) }; @@ -432,12 +413,7 @@ impl ProductionValidatorClient { start_fallback_updater_service(context.clone(), proposer_nodes.clone())?; let doppelganger_service = if config.enable_doppelganger_protection { - Some(Arc::new(DoppelgangerService::new( - context - .service_context(DOPPELGANGER_SERVICE_NAME.into()) - .log() - .clone(), - ))) + Some(Arc::new(DoppelgangerService::default())) } else { None }; @@ -451,16 +427,14 @@ impl ProductionValidatorClient { slot_clock.clone(), &config.validator_store, context.executor.clone(), - log.clone(), )); // Ensure all validators are registered in doppelganger protection. validator_store.register_all_in_doppelganger_protection_if_enabled()?; info!( - log, - "Loaded validator keypair store"; - "voting_validators" => validator_store.num_voting_validators() + voting_validators = validator_store.num_voting_validators(), + "Loaded validator keypair store" ); // Perform pruning of the slashing protection database on start-up. In case the database is @@ -483,6 +457,7 @@ impl ProductionValidatorClient { context: duties_context, enable_high_validator_count_metrics: config.enable_high_validator_count_metrics, distributed: config.distributed, + disable_attesting: config.disable_attesting, }); // Update the metrics server. @@ -512,6 +487,7 @@ impl ProductionValidatorClient { .validator_store(validator_store.clone()) .beacon_nodes(beacon_nodes.clone()) .runtime_context(context.service_context("attestation".into())) + .disable(config.disable_attesting) .build()?; let preparation_service = PreparationServiceBuilder::new() @@ -554,7 +530,6 @@ impl ProductionValidatorClient { // whole epoch! let channel_capacity = E::slots_per_epoch() as usize; let (block_service_tx, block_service_rx) = mpsc::channel(channel_capacity); - let log = self.context.log(); let api_secret = ApiSecret::create_or_open(&self.config.http_api.http_token_path)?; @@ -572,7 +547,6 @@ impl ProductionValidatorClient { config: self.config.http_api.clone(), sse_logging_components: self.context.sse_logging_components.clone(), slot_clock: self.slot_clock.clone(), - log: log.clone(), _phantom: PhantomData, }); @@ -588,12 +562,12 @@ impl ProductionValidatorClient { Some(listen_addr) } else { - info!(log, "HTTP API server is disabled"); + info!("HTTP API server is disabled"); None }; // Wait until genesis has occurred. - wait_for_genesis(&self.beacon_nodes, self.genesis_time, &self.context).await?; + wait_for_genesis(&self.beacon_nodes, self.genesis_time).await?; duties_service::start_update_service(self.duties_service.clone(), block_service_tx); @@ -628,7 +602,7 @@ impl ProductionValidatorClient { ) .map_err(|e| format!("Unable to start doppelganger service: {}", e))? } else { - info!(log, "Doppelganger protection disabled.") + info!("Doppelganger protection disabled.") } spawn_notifier(self).map_err(|e| format!("Failed to start notifier: {}", e))?; @@ -648,7 +622,6 @@ impl ProductionValidatorClient { async fn init_from_beacon_node( beacon_nodes: &BeaconNodeFallback, proposer_nodes: &BeaconNodeFallback, - context: &RuntimeContext, ) -> Result<(u64, Hash256), String> { loop { beacon_nodes.update_all_candidates().await; @@ -662,41 +635,37 @@ async fn init_from_beacon_node( if proposer_total > 0 && proposer_available == 0 { warn!( - context.log(), - "Unable to connect to a proposer node"; - "retry in" => format!("{} seconds", RETRY_DELAY.as_secs()), - "total_proposers" => proposer_total, - "available_proposers" => proposer_available, - "total_beacon_nodes" => num_total, - "available_beacon_nodes" => num_available, + retry_in = format!("{} seconds", RETRY_DELAY.as_secs()), + total_proposers = proposer_total, + available_proposers = proposer_available, + total_beacon_nodes = num_total, + available_beacon_nodes = num_available, + "Unable to connect to a proposer node" ); } if num_available > 0 && proposer_available == 0 { info!( - context.log(), - "Initialized beacon node connections"; - "total" => num_total, - "available" => num_available, + total = num_total, + available = num_available, + "Initialized beacon node connections" ); break; } else if num_available > 0 { info!( - context.log(), - "Initialized beacon node connections"; - "total" => num_total, - "available" => num_available, - "proposers_available" => proposer_available, - "proposers_total" => proposer_total, + total = num_total, + available = num_available, + proposer_available, + proposer_total, + "Initialized beacon node connections" ); break; } else { warn!( - context.log(), - "Unable to connect to a beacon node"; - "retry in" => format!("{} seconds", RETRY_DELAY.as_secs()), - "total" => num_total, - "available" => num_available, + retry_in = format!("{} seconds", RETRY_DELAY.as_secs()), + total = num_total, + available = num_available, + "Unable to connect to a beacon node" ); sleep(RETRY_DELAY).await; } @@ -717,15 +686,11 @@ async fn init_from_beacon_node( .filter_map(|(_, e)| e.request_failure()) .any(|e| e.status() == Some(StatusCode::NOT_FOUND)) { - info!( - context.log(), - "Waiting for genesis"; - ); + info!("Waiting for genesis"); } else { error!( - context.log(), - "Errors polling beacon node"; - "error" => %errors + %errors, + "Errors polling beacon node" ); } } @@ -740,7 +705,6 @@ async fn init_from_beacon_node( async fn wait_for_genesis( beacon_nodes: &BeaconNodeFallback, genesis_time: u64, - context: &RuntimeContext, ) -> Result<(), String> { let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -754,28 +718,25 @@ async fn wait_for_genesis( // the slot clock. if now < genesis_time { info!( - context.log(), - "Starting node prior to genesis"; - "seconds_to_wait" => (genesis_time - now).as_secs() + seconds_to_wait = (genesis_time - now).as_secs(), + "Starting node prior to genesis" ); // Start polling the node for pre-genesis information, cancelling the polling as soon as the // timer runs out. tokio::select! { - result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time, context.log()) => result?, + result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time) => result?, () = sleep(genesis_time - now) => () }; info!( - context.log(), - "Genesis has occurred"; - "ms_since_genesis" => (genesis_time - now).as_millis() + ms_since_genesis = (genesis_time - now).as_millis(), + "Genesis has occurred" ); } else { info!( - context.log(), - "Genesis has already occurred"; - "seconds_ago" => (now - genesis_time).as_secs() + seconds_ago = (now - genesis_time).as_secs(), + "Genesis has already occurred" ); } @@ -787,7 +748,6 @@ async fn wait_for_genesis( async fn poll_whilst_waiting_for_genesis( beacon_nodes: &BeaconNodeFallback, genesis_time: Duration, - log: &Logger, ) -> Result<(), String> { loop { match beacon_nodes @@ -801,19 +761,17 @@ async fn poll_whilst_waiting_for_genesis( if !is_staking { error!( - log, - "Staking is disabled for beacon node"; - "msg" => "this will caused missed duties", - "info" => "see the --staking CLI flag on the beacon node" + msg = "this will caused missed duties", + info = "see the --staking CLI flag on the beacon node", + "Staking is disabled for beacon node" ); } if now < genesis_time { info!( - log, - "Waiting for genesis"; - "bn_staking_enabled" => is_staking, - "seconds_to_wait" => (genesis_time - now).as_secs() + bn_staking_enabled = is_staking, + seconds_to_wait = (genesis_time - now).as_secs(), + "Waiting for genesis" ); } else { break Ok(()); @@ -821,9 +779,8 @@ async fn poll_whilst_waiting_for_genesis( } Err(e) => { error!( - log, - "Error polling beacon node"; - "error" => %e + error = %e, + "Error polling beacon node" ); } } diff --git a/validator_client/src/notifier.rs b/validator_client/src/notifier.rs index ff66517795..75b3d46457 100644 --- a/validator_client/src/notifier.rs +++ b/validator_client/src/notifier.rs @@ -1,8 +1,8 @@ use crate::{DutiesService, ProductionValidatorClient}; use metrics::set_gauge; -use slog::{debug, error, info, Logger}; use slot_clock::SlotClock; use tokio::time::{sleep, Duration}; +use tracing::{debug, error, info}; use types::EthSpec; /// Spawns a notifier service which periodically logs information about the node. @@ -14,14 +14,12 @@ pub fn spawn_notifier(client: &ProductionValidatorClient) -> Resu let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot); let interval_fut = async move { - let log = context.log(); - loop { if let Some(duration_to_next_slot) = duties_service.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot + slot_duration / 2).await; - notify(&duties_service, log).await; + notify(&duties_service).await; } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; continue; @@ -34,10 +32,7 @@ pub fn spawn_notifier(client: &ProductionValidatorClient) -> Resu } /// Performs a single notification routine. -async fn notify( - duties_service: &DutiesService, - log: &Logger, -) { +async fn notify(duties_service: &DutiesService) { let (candidate_info, num_available, num_synced) = duties_service.beacon_nodes.get_notifier_info().await; let num_total = candidate_info.len(); @@ -61,20 +56,18 @@ async fn notify( .map(|candidate| candidate.endpoint.as_str()) .unwrap_or("None"); info!( - log, - "Connected to beacon node(s)"; - "primary" => primary, - "total" => num_total, - "available" => num_available, - "synced" => num_synced, + primary, + total = num_total, + available = num_available, + synced = num_synced, + "Connected to beacon node(s)" ) } else { error!( - log, - "No synced beacon nodes"; - "total" => num_total, - "available" => num_available, - "synced" => num_synced, + total = num_total, + available = num_available, + synced = num_synced, + "No synced beacon nodes" ) } if num_synced_fallback > 0 { @@ -86,23 +79,21 @@ async fn notify( for info in candidate_info { if let Ok(health) = info.health { debug!( - log, - "Beacon node info"; - "status" => "Connected", - "index" => info.index, - "endpoint" => info.endpoint, - "head_slot" => %health.head, - "is_optimistic" => ?health.optimistic_status, - "execution_engine_status" => ?health.execution_status, - "health_tier" => %health.health_tier, + status = "Connected", + index = info.index, + endpoint = info.endpoint, + head_slot = %health.head, + is_optimistic = ?health.optimistic_status, + execution_engine_status = ?health.execution_status, + health_tier = %health.health_tier, + "Beacon node info" ); } else { debug!( - log, - "Beacon node info"; - "status" => "Disconnected", - "index" => info.index, - "endpoint" => info.endpoint, + status = "Disconnected", + index = info.index, + endpoint = info.endpoint, + "Beacon node info" ); } } @@ -116,45 +107,44 @@ async fn notify( let doppelganger_detecting_validators = duties_service.doppelganger_detecting_count(); if doppelganger_detecting_validators > 0 { - info!(log, "Listening for doppelgangers"; "doppelganger_detecting_validators" => doppelganger_detecting_validators) + info!( + doppelganger_detecting_validators, + "Listening for doppelgangers" + ) } if total_validators == 0 { info!( - log, - "No validators present"; - "msg" => "see `lighthouse vm create --help` or the HTTP API documentation" + msg = "see `lighthouse vm create --help` or the HTTP API documentation", + "No validators present" ) } else if total_validators == attesting_validators { info!( - log, - "All validators active"; - "current_epoch_proposers" => proposing_validators, - "active_validators" => attesting_validators, - "total_validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + current_epoch_proposers = proposing_validators, + active_validators = attesting_validators, + total_validators = total_validators, + %epoch, + %slot, + "All validators active" ); } else if attesting_validators > 0 { info!( - log, - "Some validators active"; - "current_epoch_proposers" => proposing_validators, - "active_validators" => attesting_validators, - "total_validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + current_epoch_proposers = proposing_validators, + active_validators = attesting_validators, + total_validators = total_validators, + %epoch, + %slot, + "Some validators active" ); } else { info!( - log, - "Awaiting activation"; - "validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + validators = total_validators, + %epoch, + %slot, + "Awaiting activation" ); } } else { - error!(log, "Unable to read slot clock"); + error!("Unable to read slot clock"); } } diff --git a/validator_client/validator_services/Cargo.toml b/validator_client/validator_services/Cargo.toml index b4495a7c81..4b023bb40a 100644 --- a/validator_client/validator_services/Cargo.toml +++ b/validator_client/validator_services/Cargo.toml @@ -13,11 +13,12 @@ environment = { workspace = true } eth2 = { workspace = true } futures = { workspace = true } graffiti_file = { workspace = true } +logging = { workspace = true } parking_lot = { workspace = true } safe_arith = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } validator_metrics = { workspace = true } diff --git a/validator_client/validator_services/src/attestation_service.rs b/validator_client/validator_services/src/attestation_service.rs index 9a6f94d52b..8e098b81b0 100644 --- a/validator_client/validator_services/src/attestation_service.rs +++ b/validator_client/validator_services/src/attestation_service.rs @@ -3,12 +3,13 @@ use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; use either::Either; use environment::RuntimeContext; use futures::future::join_all; -use slog::{crit, debug, error, info, trace, warn}; +use logging::crit; use slot_clock::SlotClock; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; use tokio::time::{sleep, sleep_until, Duration, Instant}; +use tracing::{debug, error, info, trace, warn}; use tree_hash::TreeHash; use types::{Attestation, AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot}; use validator_store::{Error as ValidatorStoreError, ValidatorStore}; @@ -21,6 +22,7 @@ pub struct AttestationServiceBuilder { slot_clock: Option, beacon_nodes: Option>>, context: Option>, + disable: bool, } impl AttestationServiceBuilder { @@ -31,6 +33,7 @@ impl AttestationServiceBuilder { slot_clock: None, beacon_nodes: None, context: None, + disable: false, } } @@ -59,6 +62,11 @@ impl AttestationServiceBuilder { self } + pub fn disable(mut self, disable: bool) -> Self { + self.disable = disable; + self + } + pub fn build(self) -> Result, String> { Ok(AttestationService { inner: Arc::new(Inner { @@ -77,6 +85,7 @@ impl AttestationServiceBuilder { context: self .context .ok_or("Cannot build AttestationService without runtime_context")?, + disable: self.disable, }), }) } @@ -89,6 +98,7 @@ pub struct Inner { slot_clock: T, beacon_nodes: Arc>, context: RuntimeContext, + disable: bool, } /// Attempts to produce attestations for all known validators 1/3rd of the way through each slot. @@ -119,7 +129,10 @@ impl Deref for AttestationService { impl AttestationService { /// Starts the service which periodically produces attestations. pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { - let log = self.context.log().clone(); + if self.disable { + info!("Attestation service disabled"); + return Ok(()); + } let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self @@ -128,9 +141,8 @@ impl AttestationService { .ok_or("Unable to determine duration to next slot")?; info!( - log, - "Attestation production service started"; - "next_update_millis" => duration_to_next_slot.as_millis() + next_update_millis = duration_to_next_slot.as_millis(), + "Attestation production service started" ); let executor = self.context.executor.clone(); @@ -139,22 +151,14 @@ impl AttestationService { loop { if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot + slot_duration / 3).await; - let log = self.context.log(); if let Err(e) = self.spawn_attestation_tasks(slot_duration) { - crit!( - log, - "Failed to spawn attestation tasks"; - "error" => e - ) + crit!(error = e, "Failed to spawn attestation tasks") } else { - trace!( - log, - "Spawned attestation tasks"; - ) + trace!("Spawned attestation tasks"); } } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; continue; @@ -236,7 +240,6 @@ impl AttestationService { validator_duties: Vec, aggregate_production_instant: Instant, ) -> Result<(), ()> { - let log = self.context.log(); let attestations_timer = validator_metrics::start_timer_vec( &validator_metrics::ATTESTATION_SERVICE_TIMES, &[validator_metrics::ATTESTATIONS], @@ -256,11 +259,10 @@ impl AttestationService { .await .map_err(move |e| { crit!( - log, - "Error during attestation routine"; - "error" => format!("{:?}", e), - "committee_index" => committee_index, - "slot" => slot.as_u64(), + error = format!("{:?}", e), + committee_index, + slot = slot.as_u64(), + "Error during attestation routine" ) })?; @@ -293,11 +295,10 @@ impl AttestationService { .await .map_err(move |e| { crit!( - log, - "Error during attestation routine"; - "error" => format!("{:?}", e), - "committee_index" => committee_index, - "slot" => slot.as_u64(), + error = format!("{:?}", e), + committee_index, + slot = slot.as_u64(), + "Error during attestation routine" ) })?; } @@ -323,8 +324,6 @@ impl AttestationService { committee_index: CommitteeIndex, validator_duties: &[DutyAndProof], ) -> Result, String> { - let log = self.context.log(); - if validator_duties.is_empty() { return Ok(None); } @@ -360,13 +359,12 @@ impl AttestationService { // Ensure that the attestation matches the duties. if !duty.match_attestation_data::(attestation_data, &self.context.eth2_config.spec) { crit!( - log, - "Inconsistent validator duties during signing"; - "validator" => ?duty.pubkey, - "duty_slot" => duty.slot, - "attestation_slot" => attestation_data.slot, - "duty_index" => duty.committee_index, - "attestation_index" => attestation_data.index, + validator = ?duty.pubkey, + duty_slot = %duty.slot, + attestation_slot = %attestation_data.slot, + duty_index = duty.committee_index, + attestation_index = attestation_data.index, + "Inconsistent validator duties during signing" ); return None; } @@ -383,11 +381,10 @@ impl AttestationService { Ok(attestation) => attestation, Err(err) => { crit!( - log, - "Invalid validator duties during signing"; - "validator" => ?duty.pubkey, - "duty" => ?duty, - "err" => ?err, + validator = ?duty.pubkey, + ?duty, + ?err, + "Invalid validator duties during signing" ); return None; } @@ -408,24 +405,22 @@ impl AttestationService { // A pubkey can be missing when a validator was recently // removed via the API. warn!( - log, - "Missing pubkey for attestation"; - "info" => "a validator may have recently been removed from this VC", - "pubkey" => ?pubkey, - "validator" => ?duty.pubkey, - "committee_index" => committee_index, - "slot" => slot.as_u64(), + info = "a validator may have recently been removed from this VC", + pubkey = ?pubkey, + validator = ?duty.pubkey, + committee_index = committee_index, + slot = slot.as_u64(), + "Missing pubkey for attestation" ); None } Err(e) => { crit!( - log, - "Failed to sign attestation"; - "error" => ?e, - "validator" => ?duty.pubkey, - "committee_index" => committee_index, - "slot" => slot.as_u64(), + error = ?e, + validator = ?duty.pubkey, + committee_index, + slot = slot.as_u64(), + "Failed to sign attestation" ); None } @@ -440,7 +435,7 @@ impl AttestationService { .unzip(); if attestations.is_empty() { - warn!(log, "No attestations were published"); + warn!("No attestations were published"); return Ok(None); } let fork_name = self @@ -468,12 +463,11 @@ impl AttestationService { // This shouldn't happen unless BN and VC are out of sync with // respect to the Electra fork. error!( - log, - "Unable to convert to SingleAttestation"; - "error" => ?e, - "committee_index" => attestation_data.index, - "slot" => slot.as_u64(), - "type" => "unaggregated", + error = ?e, + committee_index = attestation_data.index, + slot = slot.as_u64(), + "type" = "unaggregated", + "Unable to convert to SingleAttestation" ); None } @@ -496,22 +490,20 @@ impl AttestationService { .await { Ok(()) => info!( - log, - "Successfully published attestations"; - "count" => attestations.len(), - "validator_indices" => ?validator_indices, - "head_block" => ?attestation_data.beacon_block_root, - "committee_index" => attestation_data.index, - "slot" => attestation_data.slot.as_u64(), - "type" => "unaggregated", + count = attestations.len(), + validator_indices = ?validator_indices, + head_block = ?attestation_data.beacon_block_root, + committee_index = attestation_data.index, + slot = attestation_data.slot.as_u64(), + "type" = "unaggregated", + "Successfully published attestations" ), Err(e) => error!( - log, - "Unable to publish attestations"; - "error" => %e, - "committee_index" => attestation_data.index, - "slot" => slot.as_u64(), - "type" => "unaggregated", + error = %e, + committee_index = attestation_data.index, + slot = slot.as_u64(), + "type" = "unaggregated", + "Unable to publish attestations" ), } @@ -537,8 +529,6 @@ impl AttestationService { committee_index: CommitteeIndex, validator_duties: &[DutyAndProof], ) -> Result<(), String> { - let log = self.context.log(); - if !validator_duties .iter() .any(|duty_and_proof| duty_and_proof.selection_proof.is_some()) @@ -596,7 +586,7 @@ impl AttestationService { let selection_proof = duty_and_proof.selection_proof.as_ref()?; if !duty.match_attestation_data::(attestation_data, &self.context.eth2_config.spec) { - crit!(log, "Inconsistent validator duties during signing"); + crit!("Inconsistent validator duties during signing"); return None; } @@ -614,19 +604,14 @@ impl AttestationService { Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { // A pubkey can be missing when a validator was recently // removed via the API. - debug!( - log, - "Missing pubkey for aggregate"; - "pubkey" => ?pubkey, - ); + debug!(?pubkey, "Missing pubkey for aggregate"); None } Err(e) => { crit!( - log, - "Failed to sign aggregate"; - "error" => ?e, - "pubkey" => ?duty.pubkey, + error = ?e, + pubkey = ?duty.pubkey, + "Failed to sign aggregate" ); None } @@ -670,14 +655,13 @@ impl AttestationService { for signed_aggregate_and_proof in signed_aggregate_and_proofs { let attestation = signed_aggregate_and_proof.message().aggregate(); info!( - log, - "Successfully published attestation"; - "aggregator" => signed_aggregate_and_proof.message().aggregator_index(), - "signatures" => attestation.num_set_aggregation_bits(), - "head_block" => format!("{:?}", attestation.data().beacon_block_root), - "committee_index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), - "type" => "aggregated", + aggregator = signed_aggregate_and_proof.message().aggregator_index(), + signatures = attestation.num_set_aggregation_bits(), + head_block = format!("{:?}", attestation.data().beacon_block_root), + committee_index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "type" = "aggregated", + "Successfully published attestation" ); } } @@ -685,13 +669,12 @@ impl AttestationService { for signed_aggregate_and_proof in signed_aggregate_and_proofs { let attestation = &signed_aggregate_and_proof.message().aggregate(); crit!( - log, - "Failed to publish attestation"; - "error" => %e, - "aggregator" => signed_aggregate_and_proof.message().aggregator_index(), - "committee_index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), - "type" => "aggregated", + error = %e, + aggregator = signed_aggregate_and_proof.message().aggregator_index(), + committee_index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "type" = "aggregated", + "Failed to publish attestation" ); } } diff --git a/validator_client/validator_services/src/block_service.rs b/validator_client/validator_services/src/block_service.rs index 60eb0361ad..d2dbbb656e 100644 --- a/validator_client/validator_services/src/block_service.rs +++ b/validator_client/validator_services/src/block_service.rs @@ -4,7 +4,7 @@ use environment::RuntimeContext; use eth2::types::{FullBlockContents, PublishBlockRequest}; use eth2::{BeaconNodeHttpClient, StatusCode}; use graffiti_file::{determine_graffiti, GraffitiFile}; -use slog::{crit, debug, error, info, trace, warn, Logger}; +use logging::crit; use slot_clock::SlotClock; use std::fmt::Debug; use std::future::Future; @@ -12,6 +12,7 @@ use std::ops::Deref; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; +use tracing::{debug, error, info, trace, warn}; use types::{ BlindedBeaconBlock, BlockType, EthSpec, Graffiti, PublicKeyBytes, SignedBlindedBeaconBlock, Slot, @@ -219,9 +220,7 @@ impl BlockService { self, mut notification_rx: mpsc::Receiver, ) -> Result<(), String> { - let log = self.context.log().clone(); - - info!(log, "Block production service started"); + info!("Block production service started"); let executor = self.inner.context.executor.clone(); @@ -230,7 +229,7 @@ impl BlockService { while let Some(notif) = notification_rx.recv().await { self.do_update(notif).await.ok(); } - debug!(log, "Block service shutting down"); + debug!("Block service shutting down"); }, "block_service", ); @@ -240,64 +239,54 @@ impl BlockService { /// Attempt to produce a block for any block producers in the `ValidatorStore`. async fn do_update(&self, notification: BlockServiceNotification) -> Result<(), ()> { - let log = self.context.log(); let _timer = validator_metrics::start_timer_vec( &validator_metrics::BLOCK_SERVICE_TIMES, &[validator_metrics::FULL_UPDATE], ); let slot = self.slot_clock.now().ok_or_else(move || { - crit!(log, "Duties manager failed to read slot clock"); + crit!("Duties manager failed to read slot clock"); })?; if notification.slot != slot { warn!( - log, - "Skipping block production for expired slot"; - "current_slot" => slot.as_u64(), - "notification_slot" => notification.slot.as_u64(), - "info" => "Your machine could be overloaded" + current_slot = slot.as_u64(), + notification_slot = notification.slot.as_u64(), + info = "Your machine could be overloaded", + "Skipping block production for expired slot" ); return Ok(()); } if slot == self.context.eth2_config.spec.genesis_slot { debug!( - log, - "Not producing block at genesis slot"; - "proposers" => format!("{:?}", notification.block_proposers), + proposers = format!("{:?}", notification.block_proposers), + "Not producing block at genesis slot" ); return Ok(()); } - trace!( - log, - "Block service update started"; - "slot" => slot.as_u64() - ); + trace!(slot = slot.as_u64(), "Block service update started"); let proposers = notification.block_proposers; if proposers.is_empty() { trace!( - log, - "No local block proposers for this slot"; - "slot" => slot.as_u64() + slot = slot.as_u64(), + "No local block proposers for this slot" ) } else if proposers.len() > 1 { error!( - log, - "Multiple block proposers for this slot"; - "action" => "producing blocks for all proposers", - "num_proposers" => proposers.len(), - "slot" => slot.as_u64(), + action = "producing blocks for all proposers", + num_proposers = proposers.len(), + slot = slot.as_u64(), + "Multiple block proposers for this slot" ) } for validator_pubkey in proposers { let builder_boost_factor = self.get_builder_boost_factor(&validator_pubkey); let service = self.clone(); - let log = log.clone(); self.inner.context.executor.spawn( async move { let result = service @@ -308,11 +297,10 @@ impl BlockService { Ok(_) => {} Err(BlockError::Recoverable(e)) | Err(BlockError::Irrecoverable(e)) => { error!( - log, - "Error whilst producing block"; - "error" => ?e, - "block_slot" => ?slot, - "info" => "block v3 proposal failed, this error may or may not result in a missed block" + error = ?e, + block_slot = ?slot, + info = "block v3 proposal failed, this error may or may not result in a missed block", + "Error whilst producing block" ); } } @@ -332,7 +320,6 @@ impl BlockService { validator_pubkey: &PublicKeyBytes, unsigned_block: UnsignedBlock, ) -> Result<(), BlockError> { - let log = self.context.log(); let signing_timer = validator_metrics::start_timer(&validator_metrics::BLOCK_SIGNING_TIMES); let res = match unsigned_block { @@ -357,11 +344,10 @@ impl BlockService { // A pubkey can be missing when a validator was recently removed // via the API. warn!( - log, - "Missing pubkey for block"; - "info" => "a validator may have recently been removed from this VC", - "pubkey" => ?pubkey, - "slot" => ?slot + info = "a validator may have recently been removed from this VC", + ?pubkey, + ?slot, + "Missing pubkey for block" ); return Ok(()); } @@ -377,10 +363,9 @@ impl BlockService { Duration::from_secs_f64(signing_timer.map_or(0.0, |t| t.stop_and_record())).as_millis(); info!( - log, - "Publishing signed block"; - "slot" => slot.as_u64(), - "signing_time_ms" => signing_time_ms, + slot = slot.as_u64(), + signing_time_ms = signing_time_ms, + "Publishing signed block" ); // Publish block with first available beacon node. @@ -396,13 +381,12 @@ impl BlockService { .await?; info!( - log, - "Successfully published block"; - "block_type" => ?signed_block.block_type(), - "deposits" => signed_block.num_deposits(), - "attestations" => signed_block.num_attestations(), - "graffiti" => ?graffiti.map(|g| g.as_utf8_lossy()), - "slot" => signed_block.slot().as_u64(), + block_type = ?signed_block.block_type(), + deposits = signed_block.num_deposits(), + attestations = signed_block.num_attestations(), + graffiti = ?graffiti.map(|g| g.as_utf8_lossy()), + slot = signed_block.slot().as_u64(), + "Successfully published block" ); Ok(()) } @@ -413,7 +397,6 @@ impl BlockService { validator_pubkey: PublicKeyBytes, builder_boost_factor: Option, ) -> Result<(), BlockError> { - let log = self.context.log(); let _timer = validator_metrics::start_timer_vec( &validator_metrics::BLOCK_SERVICE_TIMES, &[validator_metrics::BEACON_BLOCK], @@ -429,11 +412,10 @@ impl BlockService { // A pubkey can be missing when a validator was recently removed // via the API. warn!( - log, - "Missing pubkey for block randao"; - "info" => "a validator may have recently been removed from this VC", - "pubkey" => ?pubkey, - "slot" => ?slot + info = "a validator may have recently been removed from this VC", + ?pubkey, + ?slot, + "Missing pubkey for block randao" ); return Ok(()); } @@ -447,7 +429,6 @@ impl BlockService { let graffiti = determine_graffiti( &validator_pubkey, - log, self.graffiti_file.clone(), self.validator_store.graffiti(&validator_pubkey), self.graffiti, @@ -461,11 +442,7 @@ impl BlockService { proposer_nodes: self.proposer_nodes.clone(), }; - info!( - log, - "Requesting unsigned block"; - "slot" => slot.as_u64(), - ); + info!(slot = slot.as_u64(), "Requesting unsigned block"); // Request block from first responsive beacon node. // @@ -484,7 +461,6 @@ impl BlockService { graffiti, proposer_index, builder_boost_factor, - log, ) .await .map_err(|e| { @@ -514,7 +490,6 @@ impl BlockService { signed_block: &SignedBlock, beacon_node: BeaconNodeHttpClient, ) -> Result<(), BlockError> { - let log = self.context.log(); let slot = signed_block.slot(); match signed_block { SignedBlock::Full(signed_block) => { @@ -525,7 +500,7 @@ impl BlockService { beacon_node .post_beacon_blocks_v2_ssz(signed_block, None) .await - .or_else(|e| handle_block_post_error(e, slot, log))? + .or_else(|e| handle_block_post_error(e, slot))? } SignedBlock::Blinded(signed_block) => { let _post_timer = validator_metrics::start_timer_vec( @@ -535,7 +510,7 @@ impl BlockService { beacon_node .post_beacon_blinded_blocks_v2_ssz(signed_block, None) .await - .or_else(|e| handle_block_post_error(e, slot, log))? + .or_else(|e| handle_block_post_error(e, slot))? } } Ok::<_, BlockError>(()) @@ -548,7 +523,6 @@ impl BlockService { graffiti: Option, proposer_index: Option, builder_boost_factor: Option, - log: &Logger, ) -> Result, BlockError> { let (block_response, _) = beacon_node .get_validator_blocks_v3::( @@ -570,11 +544,7 @@ impl BlockService { eth2::types::ProduceBlockV3Response::Blinded(block) => UnsignedBlock::Blinded(block), }; - info!( - log, - "Received unsigned block"; - "slot" => slot.as_u64(), - ); + info!(slot = slot.as_u64(), "Received unsigned block"); if proposer_index != Some(unsigned_block.proposer_index()) { return Err(BlockError::Recoverable( "Proposer index does not match block proposer. Beacon chain re-orged".to_string(), @@ -662,23 +632,21 @@ impl SignedBlock { } } -fn handle_block_post_error(err: eth2::Error, slot: Slot, log: &Logger) -> Result<(), BlockError> { +fn handle_block_post_error(err: eth2::Error, slot: Slot) -> Result<(), BlockError> { // Handle non-200 success codes. if let Some(status) = err.status() { if status == StatusCode::ACCEPTED { info!( - log, - "Block is already known to BN or might be invalid"; - "slot" => slot, - "status_code" => status.as_u16(), + %slot, + status_code = status.as_u16(), + "Block is already known to BN or might be invalid" ); return Ok(()); } else if status.is_success() { debug!( - log, - "Block published with non-standard success code"; - "slot" => slot, - "status_code" => status.as_u16(), + %slot, + status_code = status.as_u16(), + "Block published with non-standard success code" ); return Ok(()); } diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 07a87b3ea5..fde5e18f6a 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -19,7 +19,6 @@ use eth2::types::{ use futures::{stream, StreamExt}; use parking_lot::RwLock; use safe_arith::{ArithError, SafeArith}; -use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::cmp::min; use std::collections::{hash_map, BTreeMap, HashMap, HashSet}; @@ -27,6 +26,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::{sync::mpsc::Sender, time::sleep}; +use tracing::{debug, error, info, warn}; use types::{ChainSpec, Epoch, EthSpec, Hash256, PublicKeyBytes, SelectionProof, Slot}; use validator_metrics::{get_int_gauge, set_int_gauge, ATTESTATION_DUTY}; use validator_store::{Error as ValidatorStoreError, ValidatorStore}; @@ -287,6 +287,7 @@ pub struct DutiesService { pub enable_high_validator_count_metrics: bool, /// If this validator is running in distributed mode. pub distributed: bool, + pub disable_attesting: bool, } impl DutiesService { @@ -434,7 +435,6 @@ pub fn start_update_service( * Spawn the task which keeps track of local block proposal duties. */ let duties_service = core_duties_service.clone(); - let log = core_duties_service.context.log().clone(); core_duties_service.context.executor.spawn( async move { loop { @@ -450,9 +450,8 @@ pub fn start_update_service( if let Err(e) = poll_beacon_proposers(&duties_service, &mut block_service_tx).await { error!( - log, - "Failed to poll beacon proposers"; - "error" => ?e + error = ?e, + "Failed to poll beacon proposers" ) } } @@ -460,11 +459,15 @@ pub fn start_update_service( "duties_service_proposers", ); + // Skip starting attestation duties or sync committee services. + if core_duties_service.disable_attesting { + return; + } + /* * Spawn the task which keeps track of local attestation duties. */ let duties_service = core_duties_service.clone(); - let log = core_duties_service.context.log().clone(); core_duties_service.context.executor.spawn( async move { loop { @@ -479,9 +482,8 @@ pub fn start_update_service( if let Err(e) = poll_beacon_attesters(&duties_service).await { error!( - log, - "Failed to poll beacon attesters"; - "error" => ?e + error = ?e, + "Failed to poll beacon attesters" ); } } @@ -491,15 +493,13 @@ pub fn start_update_service( // Spawn the task which keeps track of local sync committee duties. let duties_service = core_duties_service.clone(); - let log = core_duties_service.context.log().clone(); core_duties_service.context.executor.spawn( async move { loop { if let Err(e) = poll_sync_committee_duties(&duties_service).await { error!( - log, - "Failed to poll sync committee duties"; - "error" => ?e + error = ?e, + "Failed to poll sync committee duties" ); } @@ -531,8 +531,6 @@ async fn poll_validator_indices( &[validator_metrics::UPDATE_INDICES], ); - let log = duties_service.context.log(); - // Collect *all* pubkeys for resolving indices, even those undergoing doppelganger protection. // // Since doppelganger protection queries rely on validator indices it is important to ensure we @@ -598,11 +596,10 @@ async fn poll_validator_indices( match download_result { Ok(Some(response)) => { info!( - log, - "Validator exists in beacon chain"; - "pubkey" => ?pubkey, - "validator_index" => response.data.index, - "fee_recipient" => fee_recipient + ?pubkey, + validator_index = response.data.index, + fee_recipient, + "Validator exists in beacon chain" ); duties_service .validator_store @@ -626,21 +623,15 @@ async fn poll_validator_indices( .insert(pubkey, next_poll_slot); } - debug!( - log, - "Validator without index"; - "pubkey" => ?pubkey, - "fee_recipient" => fee_recipient - ) + debug!(?pubkey, fee_recipient, "Validator without index") } // Don't exit early on an error, keep attempting to resolve other indices. Err(e) => { error!( - log, - "Failed to resolve pubkey to index"; - "error" => %e, - "pubkey" => ?pubkey, - "fee_recipient" => fee_recipient + error = %e, + ?pubkey, + fee_recipient, + "Failed to resolve pubkey to index" ) } } @@ -664,8 +655,6 @@ async fn poll_beacon_attesters( &[validator_metrics::UPDATE_ATTESTERS_CURRENT_EPOCH], ); - let log = duties_service.context.log(); - let current_slot = duties_service .slot_clock .now() @@ -704,11 +693,10 @@ async fn poll_beacon_attesters( .await { error!( - log, - "Failed to download attester duties"; - "current_epoch" => current_epoch, - "request_epoch" => current_epoch, - "err" => ?e, + %current_epoch, + request_epoch = %current_epoch, + err = ?e, + "Failed to download attester duties" ) } @@ -726,11 +714,10 @@ async fn poll_beacon_attesters( .await { error!( - log, - "Failed to download attester duties"; - "current_epoch" => current_epoch, - "request_epoch" => next_epoch, - "err" => ?e, + %current_epoch, + request_epoch = %next_epoch, + err = ?e, + "Failed to download attester duties" ) } @@ -809,9 +796,8 @@ async fn poll_beacon_attesters( .await; if subscription_result.as_ref().is_ok() { debug!( - log, - "Broadcast attestation subscriptions"; - "count" => subscriptions.len(), + count = subscriptions.len(), + "Broadcast attestation subscriptions" ); for subscription_slots in subscription_slots_to_confirm { subscription_slots.record_successful_subscription_at(current_slot); @@ -819,9 +805,8 @@ async fn poll_beacon_attesters( } else if let Err(e) = subscription_result { if e.num_errors() < duties_service.beacon_nodes.num_total().await { warn!( - log, - "Some subscriptions failed"; - "error" => %e, + error = %e, + "Some subscriptions failed" ); // If subscriptions were sent to at least one node, regard that as a success. // There is some redundancy built into the subscription schedule to handle failures. @@ -830,9 +815,8 @@ async fn poll_beacon_attesters( } } else { error!( - log, - "All subscriptions failed"; - "error" => %e + error = %e, + "All subscriptions failed" ); } } @@ -860,14 +844,11 @@ async fn poll_beacon_attesters_for_epoch( local_indices: &[u64], local_pubkeys: &HashSet, ) -> Result<(), Error> { - let log = duties_service.context.log(); - // No need to bother the BN if we don't have any validators. if local_indices.is_empty() { debug!( - duties_service.context.log(), - "No validators, not downloading duties"; - "epoch" => epoch, + %epoch, + "No validators, not downloading duties" ); return Ok(()); } @@ -900,10 +881,10 @@ async fn poll_beacon_attesters_for_epoch( local_pubkeys .iter() .filter(|pubkey| { - attesters.get(pubkey).map_or(true, |duties| { + attesters.get(pubkey).is_none_or(|duties| { duties .get(&epoch) - .map_or(true, |(prior, _)| *prior != dependent_root) + .is_none_or(|(prior, _)| *prior != dependent_root) }) }) .collect::>() @@ -946,10 +927,9 @@ async fn poll_beacon_attesters_for_epoch( ); debug!( - log, - "Downloaded attester duties"; - "dependent_root" => %dependent_root, - "num_new_duties" => new_duties.len(), + %dependent_root, + num_new_duties = new_duties.len(), + "Downloaded attester duties" ); // Update the duties service with the new `DutyAndProof` messages. @@ -980,10 +960,9 @@ async fn poll_beacon_attesters_for_epoch( && prior_duty_and_proof.duty == duty_and_proof.duty { warn!( - log, - "Redundant attester duty update"; - "dependent_root" => %dependent_root, - "validator_index" => duty.validator_index, + %dependent_root, + validator_index = duty.validator_index, + "Redundant attester duty update" ); continue; } @@ -991,11 +970,10 @@ async fn poll_beacon_attesters_for_epoch( // Using `already_warned` avoids excessive logs. if dependent_root != *prior_dependent_root && already_warned.take().is_some() { warn!( - log, - "Attester duties re-org"; - "prior_dependent_root" => %prior_dependent_root, - "dependent_root" => %dependent_root, - "note" => "this may happen from time to time" + %prior_dependent_root, + %dependent_root, + note = "this may happen from time to time", + "Attester duties re-org" ) } *mut_value = (dependent_root, duty_and_proof); @@ -1031,7 +1009,7 @@ fn get_uninitialized_validators( .filter(|pubkey| { attesters .get(pubkey) - .map_or(true, |duties| !duties.contains_key(epoch)) + .is_none_or(|duties| !duties.contains_key(epoch)) }) .filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey)) .collect::>() @@ -1107,8 +1085,6 @@ async fn fill_in_selection_proofs( duties: Vec, dependent_root: Hash256, ) { - let log = duties_service.context.log(); - // Sort duties by slot in a BTreeMap. let mut duties_by_slot: BTreeMap> = BTreeMap::new(); @@ -1207,20 +1183,18 @@ async fn fill_in_selection_proofs( // A pubkey can be missing when a validator was recently // removed via the API. warn!( - log, - "Missing pubkey for duty and proof"; - "info" => "a validator may have recently been removed from this VC", - "pubkey" => ?pubkey, + info = "a validator may have recently been removed from this VC", + ?pubkey, + "Missing pubkey for duty and proof" ); // Do not abort the entire batch for a single failure. continue; } Err(e) => { error!( - log, - "Failed to produce duty and proof"; - "error" => ?e, - "msg" => "may impair attestation duties" + error = ?e, + msg = "may impair attestation duties", + "Failed to produce duty and proof" ); // Do not abort the entire batch for a single failure. continue; @@ -1245,9 +1219,8 @@ async fn fill_in_selection_proofs( // Our selection proofs are no longer relevant due to a reorg, abandon // this entire background process. debug!( - log, - "Stopping selection proof background task"; - "reason" => "re-org" + reason = "re-org", + "Stopping selection proof background task" ); return; } @@ -1270,11 +1243,10 @@ async fn fill_in_selection_proofs( let time_taken_ms = Duration::from_secs_f64(timer.map_or(0.0, |t| t.stop_and_record())).as_millis(); debug!( - log, - "Computed attestation selection proofs"; - "batch_size" => batch_size, - "lookahead_slot" => lookahead_slot, - "time_taken_ms" => time_taken_ms + batch_size, + %lookahead_slot, + time_taken_ms, + "Computed attestation selection proofs" ); } else { // Just sleep for one slot if we are unable to read the system clock, this gives @@ -1316,8 +1288,6 @@ async fn poll_beacon_proposers( &[validator_metrics::UPDATE_PROPOSERS], ); - let log = duties_service.context.log(); - let current_slot = duties_service .slot_clock .now() @@ -1333,7 +1303,6 @@ async fn poll_beacon_proposers( &initial_block_proposers, block_service_tx, &duties_service.validator_store, - log, ) .await; @@ -1372,10 +1341,9 @@ async fn poll_beacon_proposers( .collect::>(); debug!( - log, - "Downloaded proposer duties"; - "dependent_root" => %dependent_root, - "num_relevant_duties" => relevant_duties.len(), + %dependent_root, + num_relevant_duties = relevant_duties.len(), + "Downloaded proposer duties" ); if let Some((prior_dependent_root, _)) = duties_service @@ -1385,20 +1353,18 @@ async fn poll_beacon_proposers( { if dependent_root != prior_dependent_root { warn!( - log, - "Proposer duties re-org"; - "prior_dependent_root" => %prior_dependent_root, - "dependent_root" => %dependent_root, - "msg" => "this may happen from time to time" + %prior_dependent_root, + %dependent_root, + msg = "this may happen from time to time", + "Proposer duties re-org" ) } } } // Don't return early here, we still want to try and produce blocks using the cached values. Err(e) => error!( - log, - "Failed to download proposer duties"; - "err" => %e, + err = %e, + "Failed to download proposer duties" ), } @@ -1423,13 +1389,11 @@ async fn poll_beacon_proposers( &additional_block_producers, block_service_tx, &duties_service.validator_store, - log, ) .await; debug!( - log, - "Detected new block proposer"; - "current_slot" => current_slot, + %current_slot, + "Detected new block proposer" ); validator_metrics::inc_counter(&validator_metrics::PROPOSAL_CHANGED); } @@ -1450,7 +1414,6 @@ async fn notify_block_production_service( block_proposers: &HashSet, block_service_tx: &mut Sender, validator_store: &ValidatorStore, - log: &Logger, ) { let non_doppelganger_proposers = block_proposers .iter() @@ -1467,10 +1430,9 @@ async fn notify_block_production_service( .await { error!( - log, - "Failed to notify block service"; - "current_slot" => current_slot, - "error" => %e + %current_slot, + error = %e, + "Failed to notify block service" ); }; } diff --git a/validator_client/validator_services/src/preparation_service.rs b/validator_client/validator_services/src/preparation_service.rs index fe6eab3a8a..3367f2d6ca 100644 --- a/validator_client/validator_services/src/preparation_service.rs +++ b/validator_client/validator_services/src/preparation_service.rs @@ -3,7 +3,6 @@ use bls::PublicKeyBytes; use doppelganger_service::DoppelgangerStatus; use environment::RuntimeContext; use parking_lot::RwLock; -use slog::{debug, error, info, warn}; use slot_clock::SlotClock; use std::collections::HashMap; use std::hash::Hash; @@ -11,6 +10,7 @@ use std::ops::Deref; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::time::{sleep, Duration}; +use tracing::{debug, error, info, warn}; use types::{ Address, ChainSpec, EthSpec, ProposerPreparationData, SignedValidatorRegistrationData, ValidatorRegistrationData, @@ -173,13 +173,8 @@ impl PreparationService { /// Starts the service which periodically produces proposer preparations. pub fn start_proposer_prepare_service(self, spec: &ChainSpec) -> Result<(), String> { - let log = self.context.log().clone(); - let slot_duration = Duration::from_secs(spec.seconds_per_slot); - info!( - log, - "Proposer preparation service started"; - ); + info!("Proposer preparation service started"); let executor = self.context.executor.clone(); let spec = spec.clone(); @@ -192,9 +187,8 @@ impl PreparationService { .await .map_err(|e| { error!( - log, - "Error during proposer preparation"; - "error" => ?e, + error = ?e, + "Error during proposer preparation" ) }) .unwrap_or(()); @@ -203,7 +197,7 @@ impl PreparationService { if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot).await; } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } @@ -216,12 +210,7 @@ impl PreparationService { /// Starts the service which periodically sends connected beacon nodes validator registration information. pub fn start_validator_registration_service(self, spec: &ChainSpec) -> Result<(), String> { - let log = self.context.log().clone(); - - info!( - log, - "Validator registration service started"; - ); + info!("Validator registration service started"); let spec = spec.clone(); let slot_duration = Duration::from_secs(spec.seconds_per_slot); @@ -232,14 +221,14 @@ impl PreparationService { loop { // Poll the endpoint immediately to ensure fee recipients are received. if let Err(e) = self.register_validators().await { - error!(log,"Error during validator registration";"error" => ?e); + error!(error = ?e, "Error during validator registration"); } // Wait one slot if the register validator request fails or if we should not publish at the current slot. if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot).await; } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } @@ -274,7 +263,6 @@ impl PreparationService { } fn collect_preparation_data(&self, spec: &ChainSpec) -> Vec { - let log = self.context.log(); self.collect_proposal_data(|pubkey, proposal_data| { if let Some(fee_recipient) = proposal_data.fee_recipient { Some(ProposerPreparationData { @@ -285,10 +273,9 @@ impl PreparationService { } else { if spec.bellatrix_fork_epoch.is_some() { error!( - log, - "Validator is missing fee recipient"; - "msg" => "update validator_definitions.yml", - "pubkey" => ?pubkey + msg = "update validator_definitions.yml", + ?pubkey, + "Validator is missing fee recipient" ); } None @@ -336,8 +323,6 @@ impl PreparationService { &self, preparation_data: Vec, ) -> Result<(), String> { - let log = self.context.log(); - // Post the proposer preparations to the BN. let preparation_data_len = preparation_data.len(); let preparation_entries = preparation_data.as_slice(); @@ -351,14 +336,12 @@ impl PreparationService { .await { Ok(()) => debug!( - log, - "Published proposer preparation"; - "count" => preparation_data_len, + count = preparation_data_len, + "Published proposer preparation" ), Err(e) => error!( - log, - "Unable to publish proposer preparation to all beacon nodes"; - "error" => %e, + error = %e, + "Unable to publish proposer preparation to all beacon nodes" ), } Ok(()) @@ -400,8 +383,6 @@ impl PreparationService { &self, registration_keys: Vec, ) -> Result<(), String> { - let log = self.context.log(); - let registration_data_len = registration_keys.len(); let mut signed = Vec::with_capacity(registration_data_len); @@ -428,7 +409,7 @@ impl PreparationService { pubkey, } = key.clone(); - let signed_data = match self + match self .validator_store .sign_validator_registration_data(ValidatorRegistrationData { fee_recipient, @@ -442,29 +423,18 @@ impl PreparationService { Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { // A pubkey can be missing when a validator was recently // removed via the API. - debug!( - log, - "Missing pubkey for registration data"; - "pubkey" => ?pubkey, - ); + debug!(?pubkey, "Missing pubkey for registration data"); continue; } Err(e) => { error!( - log, - "Unable to sign validator registration data"; - "error" => ?e, - "pubkey" => ?pubkey + error = ?e, + ?pubkey, + "Unable to sign validator registration data" ); continue; } - }; - - self.validator_registration_cache - .write() - .insert(key, signed_data.clone()); - - signed_data + } }; signed.push(signed_data); } @@ -478,15 +448,22 @@ impl PreparationService { }) .await { - Ok(()) => info!( - log, - "Published validator registrations to the builder network"; - "count" => batch.len(), - ), + Ok(()) => { + info!( + count = batch.len(), + "Published validator registrations to the builder network" + ); + let mut guard = self.validator_registration_cache.write(); + for signed_data in batch { + guard.insert( + ValidatorRegistrationKey::from(signed_data.message.clone()), + signed_data.clone(), + ); + } + } Err(e) => warn!( - log, - "Unable to publish validator registrations to the builder network"; - "error" => %e, + error = %e, + "Unable to publish validator registrations to the builder network" ), } } diff --git a/validator_client/validator_services/src/sync.rs b/validator_client/validator_services/src/sync.rs index 1487999a39..7f27dd4263 100644 --- a/validator_client/validator_services/src/sync.rs +++ b/validator_client/validator_services/src/sync.rs @@ -2,12 +2,13 @@ use crate::duties_service::{DutiesService, Error}; use doppelganger_service::DoppelgangerStatus; use eth2::types::SyncCommitteeSelection; use futures::future::join_all; +use logging::crit; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use slog::{crit, debug, info, warn}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; use std::marker::PhantomData; use std::sync::Arc; +use tracing::{debug, info, warn}; use types::{ChainSpec, EthSpec, PublicKeyBytes, Slot, SyncDuty, SyncSelectionProof, SyncSubnetId}; use validator_store::Error as ValidatorStoreError; @@ -298,7 +299,7 @@ pub async fn poll_sync_committee_duties( // If the Altair fork is yet to be activated, do not attempt to poll for duties. if spec .altair_fork_epoch - .map_or(true, |altair_epoch| current_epoch < altair_epoch) + .is_none_or(|altair_epoch| current_epoch < altair_epoch) { return Ok(()); } @@ -415,23 +416,20 @@ pub async fn poll_sync_committee_duties_for_period Result<(), Error> { let spec = &duties_service.spec; - let log = duties_service.context.log(); // no local validators don't need to poll for sync committee if local_indices.is_empty() { debug!( - duties_service.context.log(), - "No validators, not polling for sync committee duties"; - "sync_committee_period" => sync_committee_period, + sync_committee_period, + "No validators, not polling for sync committee duties" ); return Ok(()); } debug!( - log, - "Fetching sync committee duties"; - "sync_committee_period" => sync_committee_period, - "num_validators" => local_indices.len(), + sync_committee_period, + num_validators = local_indices.len(), + "Fetching sync committee duties" ); let period_start_epoch = spec.epochs_per_sync_committee_period * sync_committee_period; @@ -453,16 +451,15 @@ pub async fn poll_sync_committee_duties_for_period res.data, Err(e) => { warn!( - log, - "Failed to download sync committee duties"; - "sync_committee_period" => sync_committee_period, - "error" => %e, + sync_committee_period, + error = %e, + "Failed to download sync committee duties" ); return Ok(()); } }; - debug!(log, "Fetched sync duties from BN"; "count" => duties.len()); + debug!(count = duties.len(), "Fetched sync duties from BN"); // Add duties to map. let committee_duties = duties_service @@ -475,14 +472,13 @@ pub async fn poll_sync_committee_duties_for_period "this could be due to a really long re-org, or a bug" + message = "this could be due to a really long re-org, or a bug", + "Sync committee duties changed" ); } updated_due_to_reorg @@ -490,10 +486,8 @@ pub async fn poll_sync_committee_duties_for_period duty.validator_index, - "sync_committee_period" => sync_committee_period, + validator_index = duty.validator_index, + sync_committee_period, "Validator in sync committee" ); *validator_duties = Some(ValidatorDuties::new(duty)); @@ -510,14 +504,11 @@ pub async fn fill_in_aggregation_proofs( current_slot: Slot, pre_compute_slot: Slot, ) { - let log = duties_service.context.log(); - debug!( - log, - "Calculating sync selection proofs"; - "period" => sync_committee_period, - "current_slot" => current_slot, - "pre_compute_slot" => pre_compute_slot + period = sync_committee_period, + %current_slot, + %pre_compute_slot, + "Calculating sync selection proofs" ); // Generate selection proofs for each validator at each slot, one slot at a time. @@ -720,124 +711,112 @@ pub async fn fill_in_aggregation_proofs( if slot < *validator_start_slot { continue; } - + let subnet_ids = match duty.subnet_ids::() { - Ok(subnet_ids) => subnet_ids, - Err(e) => { - crit!( - log, - "Arithmetic error computing subnet IDs"; - "error" => ?e, + Ok(subnet_ids) => subnet_ids, + Err(e) => { + crit!( + error = ?e, + "Arithmetic error computing subnet IDs" + ); + continue; + } + }; + + // Create futures to produce proofs. + let duties_service_ref = &duties_service; + let futures = subnet_ids.iter().map(|subnet_id| async move { + // Construct proof for prior slot. + let proof_slot = slot - 1; + + let proof = match duties_service_ref + .validator_store + .produce_sync_selection_proof(&duty.pubkey, proof_slot, *subnet_id) + .await + { + Ok(proof) => proof, + Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { + // A pubkey can be missing when a validator was recently + // removed via the API. + debug!( + ?pubkey, + pubkey = ?duty.pubkey, + slot = %proof_slot, + "Missing pubkey for sync selection proof" ); - continue; + return None; + } + Err(e) => { + warn!( + error = ?e, + pubkey = ?duty.pubkey, + slot = %proof_slot, + "Unable to sign selection proof" + ); + return None; } }; - // Create futures to produce proofs. - let duties_service_ref = &duties_service; - let futures = subnet_ids.iter().map(|subnet_id| async move { - // Construct proof for prior slot. - let proof_slot = slot - 1; - - let proof = match duties_service_ref - .validator_store - .produce_sync_selection_proof(&duty.pubkey, proof_slot, *subnet_id) - .await - { - Ok(proof) => proof, - Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { - // A pubkey can be missing when a validator was recently - // removed via the API. - debug!( - log, - "Missing pubkey for sync selection proof"; - "pubkey" => ?pubkey, - "pubkey" => ?duty.pubkey, - "slot" => proof_slot, - ); - return None; - } - Err(e) => { - warn!( - log, - "Unable to sign selection proof"; - "error" => ?e, - "pubkey" => ?duty.pubkey, - "slot" => proof_slot, - ); - return None; - } - }; - - match proof.is_aggregator::() { - Ok(true) => { - debug!( - log, - "Validator is sync aggregator"; - "validator_index" => duty.validator_index, - "slot" => proof_slot, - "subnet_id" => %subnet_id, - ); - Some(((proof_slot, *subnet_id), proof)) - } - Ok(false) => None, - Err(e) => { - warn!( - log, - "Error determining is_aggregator"; - "pubkey" => ?duty.pubkey, - "slot" => proof_slot, - "error" => ?e, - ); - None - } + match proof.is_aggregator::() { + Ok(true) => { + debug!( + validator_index = duty.validator_index, + slot = %proof_slot, + %subnet_id, + "Validator is sync aggregator" + ); + Some(((proof_slot, *subnet_id), proof)) + } + Ok(false) => None, + Err(e) => { + warn!( + pubkey = ?duty.pubkey, + slot = %proof_slot, + error = ?e, + "Error determining is_aggregator" + ); + None } - }); - - // Execute all the futures in parallel, collecting any successful results. - let proofs = join_all(futures) - .await - .into_iter() - .flatten() - .collect::>(); - - validator_proofs.push((duty.validator_index, proofs)); - } - - // Add to global storage (we add regularly so the proofs can be used ASAP). - let sync_map = duties_service.sync_duties.committees.read(); - let Some(committee_duties) = sync_map.get(&sync_committee_period) else { - debug!( - log, - "Missing sync duties"; - "period" => sync_committee_period, - ); - continue; - }; - let validators = committee_duties.validators.read(); - let num_validators_updated = validator_proofs.len(); - - for (validator_index, proofs) in validator_proofs { - if let Some(Some(duty)) = validators.get(&validator_index) { - duty.aggregation_duties.proofs.write().extend(proofs); - } else { - debug!( - log, - "Missing sync duty to update"; - "validator_index" => validator_index, - "period" => sync_committee_period, - ); } - } + }); - if num_validators_updated > 0 { + // Execute all the futures in parallel, collecting any successful results. + let proofs = join_all(futures) + .await + .into_iter() + .flatten() + .collect::>(); + + validator_proofs.push((duty.validator_index, proofs)); + } + + // Add to global storage (we add regularly so the proofs can be used ASAP). + let sync_map = duties_service.sync_duties.committees.read(); + let Some(committee_duties) = sync_map.get(&sync_committee_period) else { + debug!(period = sync_committee_period, "Missing sync duties"); + continue; + }; + let validators = committee_duties.validators.read(); + let num_validators_updated = validator_proofs.len(); + + for (validator_index, proofs) in validator_proofs { + if let Some(Some(duty)) = validators.get(&validator_index) { + duty.aggregation_duties.proofs.write().extend(proofs); + } else { debug!( - log, - "Finished computing sync selection proofs"; - "slot" => slot, - "updated_validators" => num_validators_updated, + validator_index, + period = sync_committee_period, + "Missing sync duty to update" ); } } + + if num_validators_updated > 0 { + debug!( + %slot, + updated_validators = num_validators_updated, + "Finished computing sync selection proofs" + ); + } } } diff --git a/validator_client/validator_services/src/sync_committee_service.rs b/validator_client/validator_services/src/sync_committee_service.rs index 3ab5b33b6c..d99c0d3107 100644 --- a/validator_client/validator_services/src/sync_committee_service.rs +++ b/validator_client/validator_services/src/sync_committee_service.rs @@ -4,13 +4,14 @@ use environment::RuntimeContext; use eth2::types::BlockId; use futures::future::join_all; use futures::future::FutureExt; -use slog::{crit, debug, error, info, trace, warn}; +use logging::crit; use slot_clock::SlotClock; use std::collections::HashMap; use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::time::{sleep, sleep_until, Duration, Instant}; +use tracing::{debug, error, info, trace, warn}; use types::{ ChainSpec, EthSpec, Hash256, PublicKeyBytes, Slot, SyncCommitteeSubscription, SyncContributionData, SyncDuty, SyncSelectionProof, SyncSubnetId, @@ -86,7 +87,11 @@ impl SyncCommitteeService { } pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { - let log = self.context.log().clone(); + if self.duties_service.disable_attesting { + info!("Sync committee service disabled"); + return Ok(()); + } + let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self .slot_clock @@ -94,9 +99,8 @@ impl SyncCommitteeService { .ok_or("Unable to determine duration to next slot")?; info!( - log, - "Sync committee service started"; - "next_update_millis" => duration_to_next_slot.as_millis() + next_update_millis = duration_to_next_slot.as_millis(), + "Sync committee service started" ); let executor = self.context.executor.clone(); @@ -105,7 +109,6 @@ impl SyncCommitteeService { loop { if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { // Wait for contribution broadcast interval 1/3 of the way through the slot. - let log = self.context.log(); sleep(duration_to_next_slot + slot_duration / 3).await; // Do nothing if the Altair fork has not yet occurred. @@ -115,21 +118,17 @@ impl SyncCommitteeService { if let Err(e) = self.spawn_contribution_tasks(slot_duration).await { crit!( - log, - "Failed to spawn sync contribution tasks"; - "error" => e + error = ?e, + "Failed to spawn sync contribution tasks" ) } else { - trace!( - log, - "Spawned sync contribution tasks"; - ) + trace!("Spawned sync contribution tasks") } // Do subscriptions for future slots/epochs. self.spawn_subscription_tasks(); } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } @@ -141,7 +140,6 @@ impl SyncCommitteeService { } async fn spawn_contribution_tasks(&self, slot_duration: Duration) -> Result<(), String> { - let log = self.context.log().clone(); let slot = self.slot_clock.now().ok_or("Failed to read slot clock")?; let duration_to_next_slot = self .slot_clock @@ -160,16 +158,12 @@ impl SyncCommitteeService { .sync_duties .get_duties_for_slot(slot, &self.duties_service.spec) else { - debug!(log, "No duties known for slot {}", slot); + debug!("No duties known for slot {}", slot); return Ok(()); }; if slot_duties.duties.is_empty() { - debug!( - log, - "No local validators in current sync committee"; - "slot" => slot, - ); + debug!(%slot, "No local validators in current sync committee"); return Ok(()); } @@ -196,11 +190,10 @@ impl SyncCommitteeService { Ok(block) => block.data.root, Err(errs) => { warn!( - log, + errors = errs.to_string(), + %slot, "Refusing to sign sync committee messages for an optimistic head block or \ - a block head with unknown optimistic status"; - "errors" => errs.to_string(), - "slot" => slot, + a block head with unknown optimistic status" ); return Ok(()); } @@ -246,8 +239,6 @@ impl SyncCommitteeService { beacon_block_root: Hash256, validator_duties: Vec, ) -> Result<(), ()> { - let log = self.context.log(); - // Create futures to produce sync committee signatures. let signature_futures = validator_duties.iter().map(|duty| async move { match self @@ -265,21 +256,19 @@ impl SyncCommitteeService { // A pubkey can be missing when a validator was recently // removed via the API. debug!( - log, - "Missing pubkey for sync committee signature"; - "pubkey" => ?pubkey, - "validator_index" => duty.validator_index, - "slot" => slot, + ?pubkey, + validator_index = duty.validator_index, + %slot, + "Missing pubkey for sync committee signature" ); None } Err(e) => { crit!( - log, - "Failed to sign sync committee signature"; - "validator_index" => duty.validator_index, - "slot" => slot, - "error" => ?e, + validator_index = duty.validator_index, + %slot, + error = ?e, + "Failed to sign sync committee signature" ); None } @@ -302,19 +291,17 @@ impl SyncCommitteeService { .await .map_err(|e| { error!( - log, - "Unable to publish sync committee messages"; - "slot" => slot, - "error" => %e, + %slot, + error = %e, + "Unable to publish sync committee messages" ); })?; info!( - log, - "Successfully published sync committee messages"; - "count" => committee_signatures.len(), - "head_block" => ?beacon_block_root, - "slot" => slot, + count = committee_signatures.len(), + head_block = ?beacon_block_root, + %slot, + "Successfully published sync committee messages" ); Ok(()) @@ -357,8 +344,6 @@ impl SyncCommitteeService { ) -> Result<(), ()> { sleep_until(aggregate_instant).await; - let log = self.context.log(); - let contribution = &self .beacon_nodes .first_success(|beacon_node| async move { @@ -375,20 +360,14 @@ impl SyncCommitteeService { .await .map_err(|e| { crit!( - log, - "Failed to produce sync contribution"; - "slot" => slot, - "beacon_block_root" => ?beacon_block_root, - "error" => %e, + %slot, + ?beacon_block_root, + error = %e, + "Failed to produce sync contribution" ) })? .ok_or_else(|| { - crit!( - log, - "No aggregate contribution found"; - "slot" => slot, - "beacon_block_root" => ?beacon_block_root, - ); + crit!(%slot, ?beacon_block_root, "No aggregate contribution found"); })? .data; @@ -409,20 +388,14 @@ impl SyncCommitteeService { Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { // A pubkey can be missing when a validator was recently // removed via the API. - debug!( - log, - "Missing pubkey for sync contribution"; - "pubkey" => ?pubkey, - "slot" => slot, - ); + debug!(?pubkey, %slot, "Missing pubkey for sync contribution"); None } Err(e) => { crit!( - log, - "Unable to sign sync committee contribution"; - "slot" => slot, - "error" => ?e, + %slot, + error = ?e, + "Unable to sign sync committee contribution" ); None } @@ -447,20 +420,18 @@ impl SyncCommitteeService { .await .map_err(|e| { error!( - log, - "Unable to publish signed contributions and proofs"; - "slot" => slot, - "error" => %e, + %slot, + error = %e, + "Unable to publish signed contributions and proofs" ); })?; info!( - log, - "Successfully published sync contributions"; - "subnet" => %subnet_id, - "beacon_block_root" => %beacon_block_root, - "num_signers" => contribution.aggregation_bits.num_set_bits(), - "slot" => slot, + subnet = %subnet_id, + beacon_block_root = %beacon_block_root, + num_signers = contribution.aggregation_bits.num_set_bits(), + %slot, + "Successfully published sync contributions" ); Ok(()) @@ -468,14 +439,13 @@ impl SyncCommitteeService { fn spawn_subscription_tasks(&self) { let service = self.clone(); - let log = self.context.log().clone(); + self.inner.context.executor.spawn( async move { service.publish_subscriptions().await.unwrap_or_else(|e| { error!( - log, - "Error publishing subscriptions"; - "error" => ?e, + error = ?e, + "Error publishing subscriptions" ) }); }, @@ -484,7 +454,6 @@ impl SyncCommitteeService { } async fn publish_subscriptions(self) -> Result<(), String> { - let log = self.context.log().clone(); let spec = &self.duties_service.spec; let slot = self.slot_clock.now().ok_or("Failed to read slot clock")?; @@ -521,12 +490,7 @@ impl SyncCommitteeService { let mut subscriptions = vec![]; for (duty_slot, sync_committee_period) in duty_slots { - debug!( - log, - "Fetching subscription duties"; - "duty_slot" => duty_slot, - "current_slot" => slot, - ); + debug!(%duty_slot, %slot, "Fetching subscription duties"); match self .duties_service .sync_duties @@ -539,9 +503,8 @@ impl SyncCommitteeService { )), None => { debug!( - log, - "No duties for subscription"; - "slot" => duty_slot, + slot = %duty_slot, + "No duties for subscription" ); all_succeeded = false; } @@ -549,29 +512,23 @@ impl SyncCommitteeService { } if subscriptions.is_empty() { - debug!( - log, - "No sync subscriptions to send"; - "slot" => slot, - ); + debug!(%slot, "No sync subscriptions to send"); return Ok(()); } // Post subscriptions to BN. debug!( - log, - "Posting sync subscriptions to BN"; - "count" => subscriptions.len(), + count = subscriptions.len(), + "Posting sync subscriptions to BN" ); let subscriptions_slice = &subscriptions; for subscription in subscriptions_slice { debug!( - log, - "Subscription"; - "validator_index" => subscription.validator_index, - "validator_sync_committee_indices" => ?subscription.sync_committee_indices, - "until_epoch" => subscription.until_epoch, + validator_index = subscription.validator_index, + validator_sync_committee_indices = ?subscription.sync_committee_indices, + until_epoch = %subscription.until_epoch, + "Subscription" ); } @@ -585,10 +542,9 @@ impl SyncCommitteeService { .await { error!( - log, - "Unable to post sync committee subscriptions"; - "slot" => slot, - "error" => %e, + %slot, + error = %e, + "Unable to post sync committee subscriptions" ); all_succeeded = false; } diff --git a/validator_client/validator_store/Cargo.toml b/validator_client/validator_store/Cargo.toml index 99c3025a30..1338c2a07e 100644 --- a/validator_client/validator_store/Cargo.toml +++ b/validator_client/validator_store/Cargo.toml @@ -12,12 +12,13 @@ path = "src/lib.rs" account_utils = { workspace = true } doppelganger_service = { workspace = true } initialized_validators = { workspace = true } +logging = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } signing_method = { workspace = true } slashing_protection = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } task_executor = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_metrics = { workspace = true } diff --git a/validator_client/validator_store/src/lib.rs b/validator_client/validator_store/src/lib.rs index 712127abb5..d59918657b 100644 --- a/validator_client/validator_store/src/lib.rs +++ b/validator_client/validator_store/src/lib.rs @@ -1,18 +1,19 @@ use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}; use doppelganger_service::{DoppelgangerService, DoppelgangerStatus, DoppelgangerValidatorStore}; use initialized_validators::InitializedValidators; +use logging::crit; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; use signing_method::{Error as SigningError, SignableMessage, SigningContext, SigningMethod}; use slashing_protection::{ interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase, }; -use slog::{crit, error, info, warn, Logger}; use slot_clock::SlotClock; use std::marker::PhantomData; use std::path::Path; use std::sync::Arc; use task_executor::TaskExecutor; +use tracing::{error, info, warn}; use types::{ attestation::Error as AttestationError, graffiti::GraffitiString, AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, @@ -83,7 +84,6 @@ pub struct ValidatorStore { slashing_protection_last_prune: Arc>, genesis_validators_root: Hash256, spec: Arc, - log: Logger, doppelganger_service: Option>, slot_clock: T, fee_recipient_process: Option
, @@ -115,7 +115,6 @@ impl ValidatorStore { slot_clock: T, config: &Config, task_executor: TaskExecutor, - log: Logger, ) -> Self { Self { validators: Arc::new(RwLock::new(validators)), @@ -123,7 +122,6 @@ impl ValidatorStore { slashing_protection_last_prune: Arc::new(Mutex::new(Epoch::new(0))), genesis_validators_root, spec, - log, doppelganger_service, slot_clock, fee_recipient_process: config.fee_recipient, @@ -186,7 +184,7 @@ impl ValidatorStore { let mut validator_def = ValidatorDefinition::new_keystore_with_password( voting_keystore_path, password_storage, - graffiti.map(Into::into), + graffiti, suggested_fee_recipient, gas_limit, builder_proposals, @@ -328,7 +326,7 @@ impl ValidatorStore { .as_ref() // If there's no doppelganger service then we assume it is purposefully disabled and // declare that all keys are safe with regard to it. - .map_or(true, |doppelganger_service| { + .is_none_or(|doppelganger_service| { doppelganger_service .validator_status(validator_pubkey) .only_safe() @@ -582,10 +580,9 @@ impl ValidatorStore { // Make sure the block slot is not higher than the current slot to avoid potential attacks. if block.slot() > current_slot { warn!( - self.log, - "Not signing block with slot greater than current slot"; - "block_slot" => block.slot().as_u64(), - "current_slot" => current_slot.as_u64() + block_slot = block.slot().as_u64(), + current_slot = current_slot.as_u64(), + "Not signing block with slot greater than current slot" ); return Err(Error::GreaterThanCurrentSlot { slot: block.slot(), @@ -631,10 +628,7 @@ impl ValidatorStore { Ok(SignedBeaconBlock::from_block(block, signature)) } Ok(Safe::SameData) => { - warn!( - self.log, - "Skipping signing of previously signed block"; - ); + warn!("Skipping signing of previously signed block"); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_BLOCKS_TOTAL, &[validator_metrics::SAME_DATA], @@ -643,10 +637,9 @@ impl ValidatorStore { } Err(NotSafe::UnregisteredValidator(pk)) => { warn!( - self.log, - "Not signing block for unregistered validator"; - "msg" => "Carefully consider running with --init-slashing-protection (see --help)", - "public_key" => format!("{:?}", pk) + msg = "Carefully consider running with --init-slashing-protection (see --help)", + public_key = format!("{:?}", pk), + "Not signing block for unregistered validator" ); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_BLOCKS_TOTAL, @@ -655,11 +648,7 @@ impl ValidatorStore { Err(Error::Slashable(NotSafe::UnregisteredValidator(pk))) } Err(e) => { - crit!( - self.log, - "Not signing slashable block"; - "error" => format!("{:?}", e) - ); + crit!(error = format!("{:?}", e), "Not signing slashable block"); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_BLOCKS_TOTAL, &[validator_metrics::SLASHABLE], @@ -726,10 +715,7 @@ impl ValidatorStore { Ok(()) } Ok(Safe::SameData) => { - warn!( - self.log, - "Skipping signing of previously signed attestation" - ); + warn!("Skipping signing of previously signed attestation"); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, &[validator_metrics::SAME_DATA], @@ -738,10 +724,9 @@ impl ValidatorStore { } Err(NotSafe::UnregisteredValidator(pk)) => { warn!( - self.log, - "Not signing attestation for unregistered validator"; - "msg" => "Carefully consider running with --init-slashing-protection (see --help)", - "public_key" => format!("{:?}", pk) + msg = "Carefully consider running with --init-slashing-protection (see --help)", + public_key = format!("{:?}", pk), + "Not signing attestation for unregistered validator" ); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, @@ -751,10 +736,9 @@ impl ValidatorStore { } Err(e) => { crit!( - self.log, - "Not signing slashable attestation"; - "attestation" => format!("{:?}", attestation.data()), - "error" => format!("{:?}", e) + attestation = format!("{:?}", attestation.data()), + error = format!("{:?}", e), + "Not signing slashable attestation" ); validator_metrics::inc_counter_vec( &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, @@ -1069,13 +1053,12 @@ impl ValidatorStore { if first_run { info!( - self.log, - "Pruning slashing protection DB"; - "epoch" => current_epoch, - "msg" => "pruning may take several minutes the first time it runs" + epoch = %current_epoch, + msg = "pruning may take several minutes the first time it runs", + "Pruning slashing protection DB" ); } else { - info!(self.log, "Pruning slashing protection DB"; "epoch" => current_epoch); + info!(epoch = %current_epoch, "Pruning slashing protection DB"); } let _timer = @@ -1091,9 +1074,8 @@ impl ValidatorStore { .prune_all_signed_attestations(all_pubkeys.iter(), new_min_target_epoch) { error!( - self.log, - "Error during pruning of signed attestations"; - "error" => ?e, + error = ?e, + "Error during pruning of signed attestations" ); return; } @@ -1103,15 +1085,14 @@ impl ValidatorStore { .prune_all_signed_blocks(all_pubkeys.iter(), new_min_slot) { error!( - self.log, - "Error during pruning of signed blocks"; - "error" => ?e, + error = ?e, + "Error during pruning of signed blocks" ); return; } *last_prune = current_epoch; - info!(self.log, "Completed pruning of slashing protection DB"); + info!("Completed pruning of slashing protection DB"); } } diff --git a/watch/.gitignore b/watch/.gitignore deleted file mode 100644 index 5b6b0720c9..0000000000 --- a/watch/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.yaml diff --git a/watch/Cargo.toml b/watch/Cargo.toml deleted file mode 100644 index 41cfb58e28..0000000000 --- a/watch/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "watch" -version = "0.1.0" -edition = { workspace = true } - -[lib] -name = "watch" -path = "src/lib.rs" - -[[bin]] -name = "watch" -path = "src/main.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -axum = "0.7" -beacon_node = { workspace = true } -bls = { workspace = true } -clap = { workspace = true } -clap_utils = { workspace = true } -diesel = { version = "2.0.2", features = ["postgres", "r2d2"] } -diesel_migrations = { version = "2.0.0", features = ["postgres"] } -env_logger = { workspace = true } -eth2 = { workspace = true } -hyper = { workspace = true } -log = { workspace = true } -r2d2 = { workspace = true } -rand = { workspace = true } -reqwest = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_yaml = { workspace = true } -tokio = { workspace = true } -types = { workspace = true } -url = { workspace = true } - -[dev-dependencies] -beacon_chain = { workspace = true } -http_api = { workspace = true } -logging = { workspace = true } -network = { workspace = true } -task_executor = { workspace = true } -testcontainers = "0.15" -tokio-postgres = "0.7.5" -unused_port = { workspace = true } diff --git a/watch/README.md b/watch/README.md deleted file mode 100644 index 877cddf234..0000000000 --- a/watch/README.md +++ /dev/null @@ -1,458 +0,0 @@ -## beacon.watch - ->beacon.watch is pre-MVP and still under active development and subject to change. - -beacon.watch is an Ethereum Beacon Chain monitoring platform whose goal is to provide fast access to -data which is: -1. Not already stored natively in the Beacon Chain -2. Too specialized for Block Explorers -3. Too sensitive for public Block Explorers - - -### Requirements -- `git` -- `rust` : https://rustup.rs/ -- `libpq` : https://www.postgresql.org/download/ -- `diesel_cli` : -``` -cargo install diesel_cli --no-default-features --features postgres -``` -- `docker` : https://docs.docker.com/engine/install/ -- `docker-compose` : https://docs.docker.com/compose/install/ - -### Setup -1. Setup the database: -``` -cd postgres_docker_compose -docker-compose up -``` - -1. Ensure the tests pass: -``` -cargo test --release -``` - -1. Drop the database (if it already exists) and run the required migrations: -``` -diesel database reset --database-url postgres://postgres:postgres@localhost/dev -``` - -1. Ensure a synced Lighthouse beacon node with historical states is available -at `localhost:5052`. - -1. Run the updater daemon: -``` -cargo run --release -- run-updater -``` - -1. Start the HTTP API server: -``` -cargo run --release -- serve -``` - -1. Ensure connectivity: -``` -curl "http://localhost:5059/v1/slots/highest" -``` - -> Functionality on MacOS has not been tested. Windows is not supported. - - -### Configuration -beacon.watch can be configured through the use of a config file. -Available options can be seen in `config.yaml.default`. - -You can specify a config file during runtime: -``` -cargo run -- run-updater --config path/to/config.yaml -cargo run -- serve --config path/to/config.yaml -``` - -You can specify only the parts of the config file which you need changed. -Missing values will remain as their defaults. - -For example, if you wish to run with default settings but only wish to alter `log_level` -your config file would be: -```yaml -# config.yaml -log_level = "info" -``` - -### Available Endpoints -As beacon.watch continues to develop, more endpoints will be added. - -> In these examples any data containing information from blockprint has either been redacted or fabricated. - -#### `/v1/slots/{slot}` -```bash -curl "http://localhost:5059/v1/slots/4635296" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/slots?start_slot={}&end_slot={}` -```bash -curl "http://localhost:5059/v1/slots?start_slot=4635296&end_slot=4635297" -``` -```json -[ - { - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "skipped": false, - "beacon_block": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182" - }, - { - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" - } -] -``` - -#### `/v1/slots/lowest` -```bash -curl "http://localhost:5059/v1/slots/lowest" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/slots/highest` -```bash -curl "http://localhost:5059/v1/slots/highest" -``` -```json -{ - "slot": "4635358", - "root": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b", - "skipped": false, - "beacon_block": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b" -} -``` - -#### `v1/slots/{slot}/block` -```bash -curl "http://localhost:5059/v1/slots/4635296/block" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/{block_id}` -```bash -curl "http://localhost:5059/v1/blocks/4635296" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks?start_slot={}&end_slot={}` -```bash -curl "http://localhost:5059/v1/blocks?start_slot=4635296&end_slot=4635297" -``` -```json -[ - { - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "parent_root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" - }, - { - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" - } -] -``` - -#### `/v1/blocks/{block_id}/previous` -```bash -curl "http://localhost:5059/v1/blocks/4635297/previous" -# OR -curl "http://localhost:5059/v1/blocks/0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182/previous" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/{block_id}/next` -```bash -curl "http://localhost:5059/v1/blocks/4635296/next" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/next" -``` -```json -{ - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "parent_root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/blocks/lowest` -```bash -curl "http://localhost:5059/v1/blocks/lowest" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/highest` -```bash -curl "http://localhost:5059/v1/blocks/highest" -``` -```json -{ - "slot": "4635358", - "root": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b", - "parent_root": "0xb66e05418bb5b1d4a965c994e1f0e5b5f0d7b780e0df12f3f6321510654fa1d2" -} -``` - -#### `/v1/blocks/{block_id}/proposer` -```bash -curl "http://localhost:5059/v1/blocks/4635296/proposer" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/proposer" - -``` -```json -{ - "slot": "4635296", - "proposer_index": 223126, - "graffiti": "" -} -``` - -#### `/v1/blocks/{block_id}/rewards` -```bash -curl "http://localhost:5059/v1/blocks/4635296/reward" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/reward" - -``` -```json -{ - "slot": "4635296", - "total": 25380059, - "attestation_reward": 24351867, - "sync_committee_reward": 1028192 -} -``` - -#### `/v1/blocks/{block_id}/packing` -```bash -curl "http://localhost:5059/v1/blocks/4635296/packing" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/packing" - -``` -```json -{ - "slot": "4635296", - "available": 16152, - "included": 13101, - "prior_skip_slots": 0 -} -``` - -#### `/v1/validators/{validator}` -```bash -curl "http://localhost:5059/v1/validators/1" -# OR -curl "http://localhost:5059/v1/validators/0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" -``` -```json -{ - "index": 1, - "public_key": "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c", - "status": "active_ongoing", - "client": null, - "activation_epoch": 0, - "exit_epoch": null -} -``` - -#### `/v1/validators/{validator}/attestation/{epoch}` -```bash -curl "http://localhost:5059/v1/validators/1/attestation/144853" -# OR -curl "http://localhost:5059/v1/validators/0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c/attestation/144853" -``` -```json -{ - "index": 1, - "epoch": "144853", - "source": true, - "head": true, - "target": true -} -``` - -#### `/v1/validators/missed/{vote}/{epoch}` -```bash -curl "http://localhost:5059/v1/validators/missed/head/144853" -``` -```json -[ - 63, - 67, - 98, - ... -] -``` - -#### `/v1/validators/missed/{vote}/{epoch}/graffiti` -```bash -curl "http://localhost:5059/v1/validators/missed/head/144853/graffiti" -``` -```json -{ - "Mr F was here": 3, - "Lighthouse/v3.1.0-aa022f4": 5, - ... -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}` -```bash -curl "http://localhost:5059/v1/clients/missed/source/144853" -``` -```json -{ - "Lighthouse": 100, - "Lodestar": 100, - "Nimbus": 100, - "Prysm": 100, - "Teku": 100, - "Unknown": 100 -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}/percentages` -Note that this endpoint expresses the following: -``` -What percentage of each client implementation missed this vote? -``` - -```bash -curl "http://localhost:5059/v1/clients/missed/target/144853/percentages" -``` -```json -{ - "Lighthouse": 0.51234567890, - "Lodestar": 0.51234567890, - "Nimbus": 0.51234567890, - "Prysm": 0.09876543210, - "Teku": 0.09876543210, - "Unknown": 0.05647382910 -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}/percentages/relative` -Note that this endpoint expresses the following: -``` -For the validators which did miss this vote, what percentage of them were from each client implementation? -``` -You can check these values against the output of `/v1/clients/percentages` to see any discrepancies. - -```bash -curl "http://localhost:5059/v1/clients/missed/target/144853/percentages/relative" -``` -```json -{ - "Lighthouse": 11.11111111111111, - "Lodestar": 11.11111111111111, - "Nimbus": 11.11111111111111, - "Prysm": 16.66666666666667, - "Teku": 16.66666666666667, - "Unknown": 33.33333333333333 -} - -``` - -#### `/v1/clients` -```bash -curl "http://localhost:5059/v1/clients" -``` -```json -{ - "Lighthouse": 5000, - "Lodestar": 5000, - "Nimbus": 5000, - "Prysm": 5000, - "Teku": 5000, - "Unknown": 5000 -} -``` - -#### `/v1/clients/percentages` -```bash -curl "http://localhost:5059/v1/clients/percentages" -``` -```json -{ - "Lighthouse": 16.66666666666667, - "Lodestar": 16.66666666666667, - "Nimbus": 16.66666666666667, - "Prysm": 16.66666666666667, - "Teku": 16.66666666666667, - "Unknown": 16.66666666666667 -} -``` - -### Future work -- New tables - - `skip_slots`? - - -- More API endpoints - - `/v1/proposers?start_epoch={}&end_epoch={}` and similar - - `/v1/validators/{status}/count` - - -- Concurrently backfill and forwards fill, so forwards fill is not bottlenecked by large backfills. - - -- Better/prettier (async?) logging. - - -- Connect to a range of beacon_nodes to sync different components concurrently. -Generally, processing certain api queries such as `block_packing` and `attestation_performance` take the longest to sync. - - -### Architecture -Connection Pooling: -- 1 Pool for Updater (read and write) -- 1 Pool for HTTP Server (should be read only, although not sure if we can enforce this) diff --git a/watch/config.yaml.default b/watch/config.yaml.default deleted file mode 100644 index 131609237c..0000000000 --- a/watch/config.yaml.default +++ /dev/null @@ -1,49 +0,0 @@ ---- -database: - user: "postgres" - password: "postgres" - dbname: "dev" - default_dbname: "postgres" - host: "localhost" - port: 5432 - connect_timeout_millis: 2000 - -server: - listen_addr: "127.0.0.1" - listen_port: 5059 - -updater: - # The URL of the Beacon Node to perform sync tasks with. - # Cannot yet accept multiple beacon nodes. - beacon_node_url: "http://localhost:5052" - # The number of epochs to backfill. Must be below 100. - max_backfill_size_epochs: 2 - # The epoch at which to stop backfilling. - backfill_stop_epoch: 0 - # Whether to sync the attestations table. - attestations: true - # Whether to sync the proposer_info table. - proposer_info: true - # Whether to sync the block_rewards table. - block_rewards: true - # Whether to sync the block_packing table. - block_packing: true - -blockprint: - # Whether to sync client information from blockprint. - enabled: false - # The URL of the blockprint server. - url: "" - # The username used to authenticate to the blockprint server. - username: "" - # The password used to authenticate to the blockprint server. - password: "" - -# Log level. -# Valid options are: -# - "trace" -# - "debug" -# - "info" -# - "warn" -# - "error" -log_level: "debug" diff --git a/watch/diesel.toml b/watch/diesel.toml deleted file mode 100644 index bfb01bccf0..0000000000 --- a/watch/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/database/schema.rs" diff --git a/watch/migrations/.gitkeep b/watch/migrations/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/watch/migrations/00000000000000_diesel_initial_setup/down.sql b/watch/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f5260911..0000000000 --- a/watch/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/watch/migrations/00000000000000_diesel_initial_setup/up.sql b/watch/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1a7..0000000000 --- a/watch/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/watch/migrations/2022-01-01-000000_canonical_slots/down.sql b/watch/migrations/2022-01-01-000000_canonical_slots/down.sql deleted file mode 100644 index 551ed6605c..0000000000 --- a/watch/migrations/2022-01-01-000000_canonical_slots/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE canonical_slots diff --git a/watch/migrations/2022-01-01-000000_canonical_slots/up.sql b/watch/migrations/2022-01-01-000000_canonical_slots/up.sql deleted file mode 100644 index 2629f11a4c..0000000000 --- a/watch/migrations/2022-01-01-000000_canonical_slots/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE canonical_slots ( - slot integer PRIMARY KEY, - root bytea NOT NULL, - skipped boolean NOT NULL, - beacon_block bytea UNIQUE -) diff --git a/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql b/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql deleted file mode 100644 index 8901956f47..0000000000 --- a/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE beacon_blocks diff --git a/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql b/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql deleted file mode 100644 index 250c667b23..0000000000 --- a/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE beacon_blocks ( - slot integer PRIMARY KEY REFERENCES canonical_slots(slot) ON DELETE CASCADE, - root bytea REFERENCES canonical_slots(beacon_block) NOT NULL, - parent_root bytea NOT NULL, - attestation_count integer NOT NULL, - transaction_count integer -) diff --git a/watch/migrations/2022-01-01-000002_validators/down.sql b/watch/migrations/2022-01-01-000002_validators/down.sql deleted file mode 100644 index 17819fc349..0000000000 --- a/watch/migrations/2022-01-01-000002_validators/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE validators diff --git a/watch/migrations/2022-01-01-000002_validators/up.sql b/watch/migrations/2022-01-01-000002_validators/up.sql deleted file mode 100644 index 69cfef6772..0000000000 --- a/watch/migrations/2022-01-01-000002_validators/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE validators ( - index integer PRIMARY KEY, - public_key bytea NOT NULL, - status text NOT NULL, - activation_epoch integer, - exit_epoch integer -) diff --git a/watch/migrations/2022-01-01-000003_proposer_info/down.sql b/watch/migrations/2022-01-01-000003_proposer_info/down.sql deleted file mode 100644 index d61330be5b..0000000000 --- a/watch/migrations/2022-01-01-000003_proposer_info/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE proposer_info diff --git a/watch/migrations/2022-01-01-000003_proposer_info/up.sql b/watch/migrations/2022-01-01-000003_proposer_info/up.sql deleted file mode 100644 index 488aedb273..0000000000 --- a/watch/migrations/2022-01-01-000003_proposer_info/up.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE proposer_info ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - proposer_index integer REFERENCES validators(index) ON DELETE CASCADE NOT NULL, - graffiti text NOT NULL -) diff --git a/watch/migrations/2022-01-01-000004_active_config/down.sql b/watch/migrations/2022-01-01-000004_active_config/down.sql deleted file mode 100644 index b4304eb7b7..0000000000 --- a/watch/migrations/2022-01-01-000004_active_config/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE active_config diff --git a/watch/migrations/2022-01-01-000004_active_config/up.sql b/watch/migrations/2022-01-01-000004_active_config/up.sql deleted file mode 100644 index 476a091160..0000000000 --- a/watch/migrations/2022-01-01-000004_active_config/up.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE active_config ( - id integer PRIMARY KEY CHECK (id=1), - config_name text NOT NULL, - slots_per_epoch integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000010_blockprint/down.sql b/watch/migrations/2022-01-01-000010_blockprint/down.sql deleted file mode 100644 index fa53325dad..0000000000 --- a/watch/migrations/2022-01-01-000010_blockprint/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE blockprint diff --git a/watch/migrations/2022-01-01-000010_blockprint/up.sql b/watch/migrations/2022-01-01-000010_blockprint/up.sql deleted file mode 100644 index 2d5741f50b..0000000000 --- a/watch/migrations/2022-01-01-000010_blockprint/up.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE blockprint ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - best_guess text NOT NULL -) diff --git a/watch/migrations/2022-01-01-000011_block_rewards/down.sql b/watch/migrations/2022-01-01-000011_block_rewards/down.sql deleted file mode 100644 index 2dc87995c7..0000000000 --- a/watch/migrations/2022-01-01-000011_block_rewards/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE block_rewards diff --git a/watch/migrations/2022-01-01-000011_block_rewards/up.sql b/watch/migrations/2022-01-01-000011_block_rewards/up.sql deleted file mode 100644 index 47cb4304f0..0000000000 --- a/watch/migrations/2022-01-01-000011_block_rewards/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE block_rewards ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - total integer NOT NULL, - attestation_reward integer NOT NULL, - sync_committee_reward integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000012_block_packing/down.sql b/watch/migrations/2022-01-01-000012_block_packing/down.sql deleted file mode 100644 index e9e7755e3e..0000000000 --- a/watch/migrations/2022-01-01-000012_block_packing/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE block_packing diff --git a/watch/migrations/2022-01-01-000012_block_packing/up.sql b/watch/migrations/2022-01-01-000012_block_packing/up.sql deleted file mode 100644 index 63a9925f92..0000000000 --- a/watch/migrations/2022-01-01-000012_block_packing/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE block_packing ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - available integer NOT NULL, - included integer NOT NULL, - prior_skip_slots integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql b/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql deleted file mode 100644 index 0f32b6b4f3..0000000000 --- a/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE suboptimal_attestations diff --git a/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql b/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql deleted file mode 100644 index 5352afefc8..0000000000 --- a/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE suboptimal_attestations ( - epoch_start_slot integer CHECK (epoch_start_slot % 32 = 0) REFERENCES canonical_slots(slot) ON DELETE CASCADE, - index integer NOT NULL REFERENCES validators(index) ON DELETE CASCADE, - source boolean NOT NULL, - head boolean NOT NULL, - target boolean NOT NULL, - PRIMARY KEY(epoch_start_slot, index) -) diff --git a/watch/migrations/2022-01-01-000020_capella/down.sql b/watch/migrations/2022-01-01-000020_capella/down.sql deleted file mode 100644 index 5903b351db..0000000000 --- a/watch/migrations/2022-01-01-000020_capella/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE beacon_blocks -DROP COLUMN withdrawal_count; diff --git a/watch/migrations/2022-01-01-000020_capella/up.sql b/watch/migrations/2022-01-01-000020_capella/up.sql deleted file mode 100644 index b52b4b0099..0000000000 --- a/watch/migrations/2022-01-01-000020_capella/up.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE beacon_blocks -ADD COLUMN withdrawal_count integer; - diff --git a/watch/postgres_docker_compose/compose.yml b/watch/postgres_docker_compose/compose.yml deleted file mode 100644 index eae4de4a2b..0000000000 --- a/watch/postgres_docker_compose/compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: "3" - -services: - postgres: - image: postgres:12.3-alpine - restart: always - environment: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - volumes: - - postgres:/var/lib/postgresql/data - ports: - - 127.0.0.1:5432:5432 - -volumes: - postgres: diff --git a/watch/src/block_packing/database.rs b/watch/src/block_packing/database.rs deleted file mode 100644 index f7375431cb..0000000000 --- a/watch/src/block_packing/database.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, block_packing}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = block_packing)] -pub struct WatchBlockPacking { - pub slot: WatchSlot, - pub available: i32, - pub included: i32, - pub prior_skip_slots: i32, -} - -/// Insert a batch of values into the `block_packing` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_block_packing( - conn: &mut PgConn, - packing: Vec, -) -> Result<(), Error> { - use self::block_packing::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in packing.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(block_packing) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Block packing inserted, count: {count}, time taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `block_packing` table where `slot` is minimum. -pub fn get_lowest_block_packing(conn: &mut PgConn) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `block_packing` table where `slot` is maximum. -pub fn get_highest_block_packing(conn: &mut PgConn) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_packing` table corresponding to a given `root_query`. -pub fn get_block_packing_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(block_packing); - - let result = join - .select((slot, available, included, prior_skip_slots)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_packing` table corresponding to a given `slot_query`. -pub fn get_block_packing_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `block_packing`. -#[allow(dead_code)] -pub fn get_unknown_block_packing( - conn: &mut PgConn, - slots_per_epoch: u64, -) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::block_packing::dsl::block_packing; - - let join = beacon_blocks.left_join(block_packing); - - let result = join - .select(slot) - .filter(root.is_null()) - // Block packing cannot be retrieved for epoch 0 so we need to exclude them. - .filter(slot.ge(slots_per_epoch as i32)) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} diff --git a/watch/src/block_packing/mod.rs b/watch/src/block_packing/mod.rs deleted file mode 100644 index 5d74fc5979..0000000000 --- a/watch/src/block_packing/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_block_packing_by_root, get_block_packing_by_slot, get_highest_block_packing, - get_lowest_block_packing, get_unknown_block_packing, insert_batch_block_packing, - WatchBlockPacking, -}; -pub use server::block_packing_routes; - -use eth2::BeaconNodeHttpClient; -use types::Epoch; - -/// Sends a request to `lighthouse/analysis/block_packing`. -/// Formats the response into a vector of `WatchBlockPacking`. -/// -/// Will fail if `start_epoch == 0`. -pub async fn get_block_packing( - bn: &BeaconNodeHttpClient, - start_epoch: Epoch, - end_epoch: Epoch, -) -> Result, Error> { - Ok(bn - .get_lighthouse_analysis_block_packing(start_epoch, end_epoch) - .await? - .into_iter() - .map(|data| WatchBlockPacking { - slot: WatchSlot::from_slot(data.slot), - available: data.available_attestations as i32, - included: data.included_attestations as i32, - prior_skip_slots: data.prior_skip_slots as i32, - }) - .collect()) -} diff --git a/watch/src/block_packing/server.rs b/watch/src/block_packing/server.rs deleted file mode 100644 index 819144562a..0000000000 --- a/watch/src/block_packing/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::block_packing::database::{ - get_block_packing_by_root, get_block_packing_by_slot, WatchBlockPacking, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_block_packing( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_block_packing_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_block_packing_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn block_packing_routes() -> Router { - Router::new().route("/v1/blocks/:block/packing", get(get_block_packing)) -} diff --git a/watch/src/block_packing/updater.rs b/watch/src/block_packing/updater.rs deleted file mode 100644 index 34847f6264..0000000000 --- a/watch/src/block_packing/updater.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::block_packing::get_block_packing; - -use eth2::types::{Epoch, EthSpec}; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING: u64 = 50; - -impl UpdateHandler { - /// Forward fills the `block_packing` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_block_packing` API with: - /// `start_epoch` -> highest completely filled epoch + 1 (or epoch of lowest beacon block) - /// `end_epoch` -> epoch of highest beacon block - /// - /// It will resync the latest epoch if it is not fully filled. - /// That is, `if highest_filled_slot % slots_per_epoch != 31` - /// This means that if the last slot of an epoch is a skip slot, the whole epoch will be - //// resynced during the next head update. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - pub async fn fill_block_packing(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `block_packing` table. - let highest_filled_slot_opt = if self.config.block_packing { - database::get_highest_block_packing(&mut conn)?.map(|packing| packing.slot) - } else { - return Err(Error::NotEnabled("block_packing".to_string())); - }; - - let mut start_epoch = if let Some(highest_filled_slot) = highest_filled_slot_opt { - if highest_filled_slot.as_slot() % self.slots_per_epoch - == self.slots_per_epoch.saturating_sub(1) - { - // The whole epoch is filled so we can begin syncing the next one. - highest_filled_slot.as_slot().epoch(self.slots_per_epoch) + 1 - } else { - // The epoch is only partially synced. Try to sync it fully. - highest_filled_slot.as_slot().epoch(self.slots_per_epoch) - } - } else { - // No entries in the `block_packing` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = database::get_lowest_beacon_block(&mut conn)? { - lowest_beacon_block - .slot - .as_slot() - .epoch(self.slots_per_epoch) - } else { - // There are no blocks in the database, do not fill the `block_packing` table. - warn!("Refusing to fill block packing as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `get_block_packing` API endpoint cannot accept `start_epoch == 0`. - if start_epoch == 0 { - start_epoch += 1 - } - - if let Some(highest_block_slot) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut end_epoch = highest_block_slot.epoch(self.slots_per_epoch); - - if start_epoch > end_epoch { - debug!("Block packing is up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) { - end_epoch = start_epoch + MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING - } - - if let Some(lowest_block_slot) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut packing = get_block_packing(&self.bn, start_epoch, end_epoch).await?; - - // Since we pull a full epoch of data but are not guaranteed to have all blocks of - // that epoch available, only insert blocks with corresponding `beacon_block`s. - packing.retain(|packing| { - packing.slot.as_slot() >= lowest_block_slot - && packing.slot.as_slot() <= highest_block_slot - }); - database::insert_batch_block_packing(&mut conn, packing)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest block when one exists".to_string(), - ))); - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_packing` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `block_packing` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `get_block_packing` function with: - /// `start_epoch` -> epoch of lowest_beacon_block - /// `end_epoch` -> epoch of lowest filled `block_packing` - 1 (or epoch of highest beacon block) - /// - /// It will resync the lowest epoch if it is not fully filled. - /// That is, `if lowest_filled_slot % slots_per_epoch != 0` - /// This means that if the last slot of an epoch is a skip slot, the whole epoch will be - //// resynced during the next head update. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - pub async fn backfill_block_packing(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_block_packing_backfill = self.config.max_backfill_size_epochs; - - // Get the slot of the lowest entry in the `block_packing` table. - let lowest_filled_slot_opt = if self.config.block_packing { - database::get_lowest_block_packing(&mut conn)?.map(|packing| packing.slot) - } else { - return Err(Error::NotEnabled("block_packing".to_string())); - }; - - let end_epoch = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - if lowest_filled_slot.as_slot() % self.slots_per_epoch == 0 { - lowest_filled_slot - .as_slot() - .epoch(self.slots_per_epoch) - .saturating_sub(Epoch::new(1)) - } else { - // The epoch is only partially synced. Try to sync it fully. - lowest_filled_slot.as_slot().epoch(self.slots_per_epoch) - } - } else { - // No entries in the `block_packing` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot().epoch(self.slots_per_epoch) - } else { - // There are no blocks in the database, do not backfill the `block_packing` table. - warn!("Refusing to backfill block packing as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_epoch <= 1 { - debug!("Block packing backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut start_epoch = lowest_block_slot.epoch(self.slots_per_epoch); - - if start_epoch >= end_epoch { - debug!("Block packing is up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_block_packing_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - if start_epoch < end_epoch.saturating_sub(max_block_packing_backfill) { - start_epoch = end_epoch.saturating_sub(max_block_packing_backfill) - } - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) { - start_epoch = end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) - } - - // The `block_packing` API cannot accept `start_epoch == 0`. - if start_epoch == 0 { - start_epoch += 1 - } - - if let Some(highest_block_slot) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut packing = get_block_packing(&self.bn, start_epoch, end_epoch).await?; - - // Only insert blocks with corresponding `beacon_block`s. - packing.retain(|packing| { - packing.slot.as_slot() >= lowest_block_slot - && packing.slot.as_slot() <= highest_block_slot - }); - - database::insert_batch_block_packing(&mut conn, packing)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest block when one exists".to_string(), - ))); - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_packing` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/block_rewards/database.rs b/watch/src/block_rewards/database.rs deleted file mode 100644 index a2bf49f3e4..0000000000 --- a/watch/src/block_rewards/database.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, block_rewards}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = block_rewards)] -pub struct WatchBlockRewards { - pub slot: WatchSlot, - pub total: i32, - pub attestation_reward: i32, - pub sync_committee_reward: i32, -} - -/// Insert a batch of values into the `block_rewards` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_block_rewards( - conn: &mut PgConn, - rewards: Vec, -) -> Result<(), Error> { - use self::block_rewards::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in rewards.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(block_rewards) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Block rewards inserted, count: {count}, time_taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `block_rewards` table where `slot` is minimum. -pub fn get_lowest_block_rewards(conn: &mut PgConn) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `block_rewards` table where `slot` is maximum. -pub fn get_highest_block_rewards(conn: &mut PgConn) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_rewards` table corresponding to a given `root_query`. -pub fn get_block_rewards_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(block_rewards); - - let result = join - .select((slot, total, attestation_reward, sync_committee_reward)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_rewards` table corresponding to a given `slot_query`. -pub fn get_block_rewards_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `block_rewards`. -#[allow(dead_code)] -pub fn get_unknown_block_rewards(conn: &mut PgConn) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::block_rewards::dsl::block_rewards; - - let join = beacon_blocks.left_join(block_rewards); - - let result = join - .select(slot) - .filter(root.is_null()) - // Block rewards cannot be retrieved for `slot == 0` so we need to exclude it. - .filter(slot.ne(0)) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} diff --git a/watch/src/block_rewards/mod.rs b/watch/src/block_rewards/mod.rs deleted file mode 100644 index 0dac88ea58..0000000000 --- a/watch/src/block_rewards/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod database; -mod server; -mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_block_rewards_by_root, get_block_rewards_by_slot, get_highest_block_rewards, - get_lowest_block_rewards, get_unknown_block_rewards, insert_batch_block_rewards, - WatchBlockRewards, -}; -pub use server::block_rewards_routes; - -use eth2::BeaconNodeHttpClient; -use types::Slot; - -/// Sends a request to `lighthouse/analysis/block_rewards`. -/// Formats the response into a vector of `WatchBlockRewards`. -/// -/// Will fail if `start_slot == 0`. -pub async fn get_block_rewards( - bn: &BeaconNodeHttpClient, - start_slot: Slot, - end_slot: Slot, -) -> Result, Error> { - Ok(bn - .get_lighthouse_analysis_block_rewards(start_slot, end_slot) - .await? - .into_iter() - .map(|data| WatchBlockRewards { - slot: WatchSlot::from_slot(data.meta.slot), - total: data.total as i32, - attestation_reward: data.attestation_rewards.total as i32, - sync_committee_reward: data.sync_committee_rewards as i32, - }) - .collect()) -} diff --git a/watch/src/block_rewards/server.rs b/watch/src/block_rewards/server.rs deleted file mode 100644 index 480346e25b..0000000000 --- a/watch/src/block_rewards/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::block_rewards::database::{ - get_block_rewards_by_root, get_block_rewards_by_slot, WatchBlockRewards, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_block_rewards( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_block_rewards_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_block_rewards_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn block_rewards_routes() -> Router { - Router::new().route("/v1/blocks/:block/rewards", get(get_block_rewards)) -} diff --git a/watch/src/block_rewards/updater.rs b/watch/src/block_rewards/updater.rs deleted file mode 100644 index e2893ad0fe..0000000000 --- a/watch/src/block_rewards/updater.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::block_rewards::get_block_rewards; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS: u64 = 1600; - -impl UpdateHandler { - /// Forward fills the `block_rewards` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_block_rewards` API with: - /// `start_slot` -> highest filled `block_rewards` + 1 (or lowest beacon block) - /// `end_slot` -> highest beacon block - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - pub async fn fill_block_rewards(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `block_rewards` table. - let highest_filled_slot_opt = if self.config.block_rewards { - database::get_highest_block_rewards(&mut conn)?.map(|reward| reward.slot) - } else { - return Err(Error::NotEnabled("block_rewards".to_string())); - }; - - let mut start_slot = if let Some(highest_filled_slot) = highest_filled_slot_opt { - highest_filled_slot.as_slot() + 1 - } else { - // No entries in the `block_rewards` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot) - { - lowest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not fill the `block_rewards` table. - warn!("Refusing to fill block rewards as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `block_rewards` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1; - } - - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - let mut end_slot = highest_beacon_block.as_slot(); - - if start_slot > end_slot { - debug!("Block rewards are up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) { - end_slot = start_slot + MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS - } - - let rewards = get_block_rewards(&self.bn, start_slot, end_slot).await?; - database::insert_batch_block_rewards(&mut conn, rewards)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_rewards` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `block_rewards` tables starting from the entry with the - /// lowest slot. - /// - /// It constructs a request to the `get_block_rewards` API with: - /// `start_slot` -> lowest_beacon_block - /// `end_slot` -> lowest filled `block_rewards` - 1 (or highest beacon block) - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - pub async fn backfill_block_rewards(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_block_reward_backfill = self.config.max_backfill_size_epochs * self.slots_per_epoch; - - // Get the slot of the lowest entry in the `block_rewards` table. - let lowest_filled_slot_opt = if self.config.block_rewards { - database::get_lowest_block_rewards(&mut conn)?.map(|reward| reward.slot) - } else { - return Err(Error::NotEnabled("block_rewards".to_string())); - }; - - let end_slot = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - lowest_filled_slot.as_slot().saturating_sub(1_u64) - } else { - // No entries in the `block_rewards` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not backfill the `block_rewards` table. - warn!("Refusing to backfill block rewards as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_slot <= 1 { - debug!("Block rewards backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = database::get_lowest_beacon_block(&mut conn)? { - let mut start_slot = lowest_block_slot.slot.as_slot(); - - if start_slot >= end_slot { - debug!("Block rewards are up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_block_reward_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - if start_slot < end_slot.saturating_sub(max_block_reward_backfill) { - start_slot = end_slot.saturating_sub(max_block_reward_backfill) - } - - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) { - start_slot = end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) - } - - // The `block_rewards` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1 - } - - let rewards = get_block_rewards(&self.bn, start_slot, end_slot).await?; - - if self.config.block_rewards { - database::insert_batch_block_rewards(&mut conn, rewards)?; - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_rewards` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/blockprint/config.rs b/watch/src/blockprint/config.rs deleted file mode 100644 index 721fa7cb19..0000000000 --- a/watch/src/blockprint/config.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const fn enabled() -> bool { - false -} - -pub const fn url() -> Option { - None -} - -pub const fn username() -> Option { - None -} - -pub const fn password() -> Option { - None -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "enabled")] - pub enabled: bool, - #[serde(default = "url")] - pub url: Option, - #[serde(default = "username")] - pub username: Option, - #[serde(default = "password")] - pub password: Option, -} - -impl Default for Config { - fn default() -> Self { - Config { - enabled: enabled(), - url: url(), - username: username(), - password: password(), - } - } -} diff --git a/watch/src/blockprint/database.rs b/watch/src/blockprint/database.rs deleted file mode 100644 index f0bc3f8ac8..0000000000 --- a/watch/src/blockprint/database.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::database::{ - self, - schema::{beacon_blocks, blockprint}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::sql_types::{Integer, Text}; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::Instant; - -type WatchConsensusClient = String; -pub fn list_consensus_clients() -> Vec { - vec![ - "Lighthouse".to_string(), - "Lodestar".to_string(), - "Nimbus".to_string(), - "Prysm".to_string(), - "Teku".to_string(), - "Unknown".to_string(), - ] -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = blockprint)] -pub struct WatchBlockprint { - pub slot: WatchSlot, - pub best_guess: WatchConsensusClient, -} - -#[derive(Debug, QueryableByName, diesel::FromSqlRow)] -#[allow(dead_code)] -pub struct WatchValidatorBlockprint { - #[diesel(sql_type = Integer)] - pub proposer_index: i32, - #[diesel(sql_type = Text)] - pub best_guess: WatchConsensusClient, - #[diesel(sql_type = Integer)] - pub slot: WatchSlot, -} - -/// Insert a batch of values into the `blockprint` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_blockprint( - conn: &mut PgConn, - prints: Vec, -) -> Result<(), Error> { - use self::blockprint::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in prints.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(blockprint) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Blockprint inserted, count: {count}, time_taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `blockprint` table where `slot` is minimum. -pub fn get_lowest_blockprint(conn: &mut PgConn) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `blockprint` table where `slot` is maximum. -pub fn get_highest_blockprint(conn: &mut PgConn) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `blockprint` table corresponding to a given `root_query`. -pub fn get_blockprint_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(blockprint); - - let result = join - .select((slot, best_guess)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `blockprint` table corresponding to a given `slot_query`. -pub fn get_blockprint_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `blockprint`. -#[allow(dead_code)] -pub fn get_unknown_blockprint(conn: &mut PgConn) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::blockprint::dsl::blockprint; - - let join = beacon_blocks.left_join(blockprint); - - let result = join - .select(slot) - .filter(root.is_null()) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} - -/// Constructs a HashMap of `index` -> `best_guess` for each validator's latest proposal at or before -/// `target_slot`. -/// Inserts `"Unknown" if no prior proposals exist. -pub fn construct_validator_blockprints_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result, Error> { - use self::blockprint::dsl::{blockprint, slot}; - - let total_validators = - database::count_validators_activated_before_slot(conn, target_slot, slots_per_epoch)? - as usize; - - let mut blockprint_map = HashMap::with_capacity(total_validators); - - let latest_proposals = - database::get_all_validators_latest_proposer_info_at_slot(conn, target_slot)?; - - let latest_proposal_slots: Vec = latest_proposals.clone().into_keys().collect(); - - let result = blockprint - .filter(slot.eq_any(latest_proposal_slots)) - .load::(conn)?; - - // Insert the validators which have available blockprints. - for print in result { - if let Some(proposer) = latest_proposals.get(&print.slot) { - blockprint_map.insert(*proposer, print.best_guess); - } - } - - // Insert the rest of the unknown validators. - for validator_index in 0..total_validators { - blockprint_map - .entry(validator_index as i32) - .or_insert_with(|| "Unknown".to_string()); - } - - Ok(blockprint_map) -} - -/// Counts the number of occurances of each `client` present in the `validators` table at or before some -/// `target_slot`. -pub fn get_validators_clients_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result, Error> { - let mut client_map: HashMap = HashMap::new(); - - // This includes all validators which were activated at or before `target_slot`. - let validator_blockprints = - construct_validator_blockprints_at_slot(conn, target_slot, slots_per_epoch)?; - - for client in list_consensus_clients() { - let count = validator_blockprints - .iter() - .filter(|(_, v)| (*v).clone() == client) - .count(); - client_map.insert(client, count); - } - - Ok(client_map) -} diff --git a/watch/src/blockprint/mod.rs b/watch/src/blockprint/mod.rs deleted file mode 100644 index 319090c656..0000000000 --- a/watch/src/blockprint/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -mod config; - -use crate::database::WatchSlot; - -use eth2::SensitiveUrl; -use reqwest::{Client, Response, Url}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::Duration; -use types::Slot; - -pub use config::Config; -pub use database::{ - get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, - get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, - WatchBlockprint, -}; -pub use server::blockprint_routes; - -const TIMEOUT: Duration = Duration::from_secs(50); - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - Reqwest(reqwest::Error), - Url(url::ParseError), - BlockprintNotSynced, - Other(String), -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::Reqwest(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Error::Url(e) - } -} - -pub struct WatchBlockprintClient { - pub client: Client, - pub server: SensitiveUrl, - pub username: Option, - pub password: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockprintSyncingResponse { - pub greatest_block_slot: Slot, - pub synced: bool, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockprintResponse { - pub proposer_index: i32, - pub slot: Slot, - pub best_guess_single: String, -} - -impl WatchBlockprintClient { - async fn get(&self, url: Url) -> Result { - let mut builder = self.client.get(url).timeout(TIMEOUT); - if let Some(username) = &self.username { - builder = builder.basic_auth(username, self.password.as_ref()); - } - let response = builder.send().await.map_err(Error::Reqwest)?; - - if !response.status().is_success() { - return Err(Error::Other(response.text().await?)); - } - - Ok(response) - } - - // Returns the `greatest_block_slot` as reported by the Blockprint server. - // Will error if the Blockprint server is not synced. - #[allow(dead_code)] - pub async fn ensure_synced(&self) -> Result { - let url = self.server.full.join("sync/")?.join("status")?; - - let response = self.get(url).await?; - - let result = response.json::().await?; - if !result.synced { - return Err(Error::BlockprintNotSynced); - } - - Ok(result.greatest_block_slot) - } - - // Pulls the latest blockprint for all validators. - #[allow(dead_code)] - pub async fn blockprint_all_validators( - &self, - highest_validator: i32, - ) -> Result, Error> { - let url = self - .server - .full - .join("validator/")? - .join("blocks/")? - .join("latest")?; - - let response = self.get(url).await?; - - let mut result = response.json::>().await?; - result.retain(|print| print.proposer_index <= highest_validator); - - let mut map: HashMap = HashMap::with_capacity(result.len()); - for print in result { - map.insert(print.proposer_index, print.best_guess_single); - } - - Ok(map) - } - - // Construct a request to the Blockprint server for a range of slots between `start_slot` and - // `end_slot`. - pub async fn get_blockprint( - &self, - start_slot: Slot, - end_slot: Slot, - ) -> Result, Error> { - let url = self - .server - .full - .join("blocks/")? - .join(&format!("{start_slot}/{end_slot}"))?; - - let response = self.get(url).await?; - - let result = response - .json::>() - .await? - .iter() - .map(|response| WatchBlockprint { - slot: WatchSlot::from_slot(response.slot), - best_guess: response.best_guess_single.clone(), - }) - .collect(); - Ok(result) - } -} diff --git a/watch/src/blockprint/server.rs b/watch/src/blockprint/server.rs deleted file mode 100644 index 488af15717..0000000000 --- a/watch/src/blockprint/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::blockprint::database::{ - get_blockprint_by_root, get_blockprint_by_slot, WatchBlockprint, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_blockprint( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_blockprint_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_blockprint_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn blockprint_routes() -> Router { - Router::new().route("/v1/blocks/:block/blockprint", get(get_blockprint)) -} diff --git a/watch/src/blockprint/updater.rs b/watch/src/blockprint/updater.rs deleted file mode 100644 index 7ec56dd9c8..0000000000 --- a/watch/src/blockprint/updater.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT: u64 = 1600; - -impl UpdateHandler { - /// Forward fills the `blockprint` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_blockprint` API with: - /// `start_slot` -> highest filled `blockprint` + 1 (or lowest beacon block) - /// `end_slot` -> highest beacon block - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - pub async fn fill_blockprint(&mut self) -> Result<(), Error> { - // Ensure blockprint in enabled. - if let Some(blockprint_client) = &self.blockprint { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `blockprint` table. - let mut start_slot = if let Some(highest_filled_slot) = - database::get_highest_blockprint(&mut conn)?.map(|print| print.slot) - { - highest_filled_slot.as_slot() + 1 - } else { - // No entries in the `blockprint` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot) - { - lowest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not fill the `blockprint` table. - warn!("Refusing to fill blockprint as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `blockprint` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1; - } - - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - let mut end_slot = highest_beacon_block.as_slot(); - - if start_slot > end_slot { - debug!("Blockprint is up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) { - end_slot = start_slot + MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT - } - - let mut prints = blockprint_client - .get_blockprint(start_slot, end_slot) - .await?; - - // Ensure the prints returned from blockprint are for slots which exist in the - // `beacon_blocks` table. - prints.retain(|print| { - database::get_beacon_block_by_slot(&mut conn, print.slot) - .ok() - .flatten() - .is_some() - }); - - database::insert_batch_blockprint(&mut conn, prints)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in either - // `blockprint` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - } - - Ok(()) - } - - /// Backfill the `blockprint` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `get_blockprint` API with: - /// `start_slot` -> lowest_beacon_block - /// `end_slot` -> lowest filled `blockprint` - 1 (or highest beacon block) - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - pub async fn backfill_blockprint(&mut self) -> Result<(), Error> { - // Ensure blockprint in enabled. - if let Some(blockprint_client) = &self.blockprint { - let mut conn = database::get_connection(&self.pool)?; - let max_blockprint_backfill = - self.config.max_backfill_size_epochs * self.slots_per_epoch; - - // Get the slot of the lowest entry in the `blockprint` table. - let end_slot = if let Some(lowest_filled_slot) = - database::get_lowest_blockprint(&mut conn)?.map(|print| print.slot) - { - lowest_filled_slot.as_slot().saturating_sub(1_u64) - } else { - // No entries in the `blockprint` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not backfill the `blockprint` table. - warn!("Refusing to backfill blockprint as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_slot <= 1 { - debug!("Blockprint backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = database::get_lowest_beacon_block(&mut conn)? { - let mut start_slot = lowest_block_slot.slot.as_slot(); - - if start_slot >= end_slot { - debug!("Blockprint are up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_blockprint_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - if start_slot < end_slot.saturating_sub(max_blockprint_backfill) { - start_slot = end_slot.saturating_sub(max_blockprint_backfill) - } - - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) { - start_slot = end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) - } - - // The `blockprint` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1 - } - - let mut prints = blockprint_client - .get_blockprint(start_slot, end_slot) - .await?; - - // Ensure the prints returned from blockprint are for slots which exist in the - // `beacon_blocks` table. - prints.retain(|print| { - database::get_beacon_block_by_slot(&mut conn, print.slot) - .ok() - .flatten() - .is_some() - }); - - database::insert_batch_blockprint(&mut conn, prints)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the `blockprint` - // table. This is a critical failure. It usually means someone has manually tampered with the - // database tables and should not occur during normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - } - Ok(()) - } -} diff --git a/watch/src/cli.rs b/watch/src/cli.rs deleted file mode 100644 index b7179efe5d..0000000000 --- a/watch/src/cli.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{config::Config, logger, server, updater}; -use clap::{Arg, ArgAction, Command}; -use clap_utils::get_color_style; - -pub const SERVE: &str = "serve"; -pub const RUN_UPDATER: &str = "run-updater"; -pub const CONFIG: &str = "config"; - -fn run_updater() -> Command { - Command::new(RUN_UPDATER).styles(get_color_style()) -} - -fn serve() -> Command { - Command::new(SERVE).styles(get_color_style()) -} - -pub fn app() -> Command { - Command::new("beacon_watch_daemon") - .author("Sigma Prime ") - .styles(get_color_style()) - .arg( - Arg::new(CONFIG) - .long(CONFIG) - .value_name("PATH_TO_CONFIG") - .help("Path to configuration file") - .action(ArgAction::Set) - .global(true), - ) - .subcommand(run_updater()) - .subcommand(serve()) -} - -pub async fn run() -> Result<(), String> { - let matches = app().get_matches(); - - let config = match matches.get_one::(CONFIG) { - Some(path) => Config::load_from_file(path.to_string())?, - None => Config::default(), - }; - - logger::init_logger(&config.log_level); - - match matches.subcommand() { - Some((RUN_UPDATER, _)) => updater::run_updater(config) - .await - .map_err(|e| format!("Failure: {:?}", e)), - Some((SERVE, _)) => server::serve(config) - .await - .map_err(|e| format!("Failure: {:?}", e)), - _ => Err("Unsupported subcommand. See --help".into()), - } -} diff --git a/watch/src/client.rs b/watch/src/client.rs deleted file mode 100644 index 43aaccde34..0000000000 --- a/watch/src/client.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::block_packing::WatchBlockPacking; -use crate::block_rewards::WatchBlockRewards; -use crate::database::models::{ - WatchBeaconBlock, WatchCanonicalSlot, WatchProposerInfo, WatchValidator, -}; -use crate::suboptimal_attestations::WatchAttestation; - -use eth2::types::BlockId; -use reqwest::Client; -use serde::de::DeserializeOwned; -use types::Hash256; -use url::Url; - -#[derive(Debug)] -pub enum Error { - Reqwest(reqwest::Error), - Url(url::ParseError), -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::Reqwest(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Error::Url(e) - } -} - -pub struct WatchHttpClient { - pub client: Client, - pub server: Url, -} - -impl WatchHttpClient { - async fn get_opt(&self, url: Url) -> Result, Error> { - let response = self.client.get(url).send().await?; - - if response.status() == 404 { - Ok(None) - } else { - response - .error_for_status()? - .json() - .await - .map_err(Into::into) - } - } - - pub async fn get_beacon_blocks( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&block_id.to_string())?; - - self.get_opt(url).await - } - - pub async fn get_lowest_canonical_slot(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("slots/")?.join("lowest")?; - - self.get_opt(url).await - } - - pub async fn get_highest_canonical_slot(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("slots/")?.join("highest")?; - - self.get_opt(url).await - } - - pub async fn get_lowest_beacon_block(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("blocks/")?.join("lowest")?; - - self.get_opt(url).await - } - - pub async fn get_highest_beacon_block(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("blocks/")?.join("highest")?; - - self.get_opt(url).await - } - - pub async fn get_next_beacon_block( - &self, - parent: Hash256, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{parent:?}/"))? - .join("next")?; - - self.get_opt(url).await - } - - pub async fn get_validator_by_index( - &self, - index: i32, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("validators/")? - .join(&format!("{index}"))?; - - self.get_opt(url).await - } - - pub async fn get_proposer_info( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("proposer")?; - - self.get_opt(url).await - } - - pub async fn get_block_reward( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("rewards")?; - - self.get_opt(url).await - } - - pub async fn get_block_packing( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("packing")?; - - self.get_opt(url).await - } - - pub async fn get_all_validators(&self) -> Result>, Error> { - let url = self.server.join("v1/")?.join("validators/")?.join("all")?; - - self.get_opt(url).await - } - - pub async fn get_attestations( - &self, - epoch: i32, - ) -> Result>, Error> { - let url = self - .server - .join("v1/")? - .join("validators/")? - .join("all/")? - .join("attestation/")? - .join(&format!("{epoch}"))?; - - self.get_opt(url).await - } -} diff --git a/watch/src/config.rs b/watch/src/config.rs deleted file mode 100644 index 4e61f9df9c..0000000000 --- a/watch/src/config.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::blockprint::Config as BlockprintConfig; -use crate::database::Config as DatabaseConfig; -use crate::server::Config as ServerConfig; -use crate::updater::Config as UpdaterConfig; - -use serde::{Deserialize, Serialize}; -use std::fs::File; - -pub const LOG_LEVEL: &str = "debug"; - -fn log_level() -> String { - LOG_LEVEL.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default)] - pub blockprint: BlockprintConfig, - #[serde(default)] - pub database: DatabaseConfig, - #[serde(default)] - pub server: ServerConfig, - #[serde(default)] - pub updater: UpdaterConfig, - /// The minimum severity for log messages. - #[serde(default = "log_level")] - pub log_level: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - blockprint: BlockprintConfig::default(), - database: DatabaseConfig::default(), - server: ServerConfig::default(), - updater: UpdaterConfig::default(), - log_level: log_level(), - } - } -} - -impl Config { - pub fn load_from_file(path_to_file: String) -> Result { - let file = - File::open(path_to_file).map_err(|e| format!("Error reading config file: {:?}", e))?; - let config: Config = serde_yaml::from_reader(file) - .map_err(|e| format!("Error parsing config file: {:?}", e))?; - Ok(config) - } -} diff --git a/watch/src/database/compat.rs b/watch/src/database/compat.rs deleted file mode 100644 index e3e9e0df6f..0000000000 --- a/watch/src/database/compat.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Implementations of PostgreSQL compatibility traits. -use crate::database::watch_types::{WatchHash, WatchPK, WatchSlot}; -use diesel::deserialize::{self, FromSql}; -use diesel::pg::{Pg, PgValue}; -use diesel::serialize::{self, Output, ToSql}; -use diesel::sql_types::{Binary, Integer}; - -macro_rules! impl_to_from_sql_int { - ($type:ty) => { - impl ToSql for $type - where - i32: ToSql, - { - fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result { - let v = i32::try_from(self.as_u64()).map_err(|e| Box::new(e))?; - >::to_sql(&v, &mut out.reborrow()) - } - } - - impl FromSql for $type { - fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { - Ok(Self::new(i32::from_sql(bytes)? as u64)) - } - } - }; -} - -macro_rules! impl_to_from_sql_binary { - ($type:ty) => { - impl ToSql for $type { - fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result { - let b = self.as_bytes(); - <&[u8] as ToSql>::to_sql(&b, &mut out.reborrow()) - } - } - - impl FromSql for $type { - fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { - Self::from_bytes(bytes.as_bytes()).map_err(|e| e.to_string().into()) - } - } - }; -} - -impl_to_from_sql_int!(WatchSlot); -impl_to_from_sql_binary!(WatchHash); -impl_to_from_sql_binary!(WatchPK); diff --git a/watch/src/database/config.rs b/watch/src/database/config.rs deleted file mode 100644 index dc0c70832f..0000000000 --- a/watch/src/database/config.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const USER: &str = "postgres"; -pub const PASSWORD: &str = "postgres"; -pub const DBNAME: &str = "dev"; -pub const DEFAULT_DBNAME: &str = "postgres"; -pub const HOST: &str = "localhost"; -pub const fn port() -> u16 { - 5432 -} -pub const fn connect_timeout_millis() -> u64 { - 2_000 // 2s -} - -fn user() -> String { - USER.to_string() -} - -fn password() -> String { - PASSWORD.to_string() -} - -fn dbname() -> String { - DBNAME.to_string() -} - -fn default_dbname() -> String { - DEFAULT_DBNAME.to_string() -} - -fn host() -> String { - HOST.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "user")] - pub user: String, - #[serde(default = "password")] - pub password: String, - #[serde(default = "dbname")] - pub dbname: String, - #[serde(default = "default_dbname")] - pub default_dbname: String, - #[serde(default = "host")] - pub host: String, - #[serde(default = "port")] - pub port: u16, - #[serde(default = "connect_timeout_millis")] - pub connect_timeout_millis: u64, -} - -impl Default for Config { - fn default() -> Self { - Self { - user: user(), - password: password(), - dbname: dbname(), - default_dbname: default_dbname(), - host: host(), - port: port(), - connect_timeout_millis: connect_timeout_millis(), - } - } -} - -impl Config { - pub fn build_database_url(&self) -> String { - format!( - "postgres://{}:{}@{}:{}/{}", - self.user, self.password, self.host, self.port, self.dbname - ) - } -} diff --git a/watch/src/database/error.rs b/watch/src/database/error.rs deleted file mode 100644 index 8c5088fa13..0000000000 --- a/watch/src/database/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use bls::Error as BlsError; -use diesel::result::{ConnectionError, Error as PgError}; -use eth2::SensitiveError; -use r2d2::Error as PoolError; -use std::fmt; -use types::BeaconStateError; - -#[derive(Debug)] -pub enum Error { - BeaconState(BeaconStateError), - Database(PgError), - DatabaseCorrupted, - InvalidSig(BlsError), - PostgresConnection(ConnectionError), - Pool(PoolError), - SensitiveUrl(SensitiveError), - InvalidRoot, - Other(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for Error { - fn from(e: BeaconStateError) -> Self { - Error::BeaconState(e) - } -} - -impl From for Error { - fn from(e: ConnectionError) -> Self { - Error::PostgresConnection(e) - } -} - -impl From for Error { - fn from(e: PgError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: PoolError) -> Self { - Error::Pool(e) - } -} - -impl From for Error { - fn from(e: BlsError) -> Self { - Error::InvalidSig(e) - } -} diff --git a/watch/src/database/mod.rs b/watch/src/database/mod.rs deleted file mode 100644 index 7193b0744a..0000000000 --- a/watch/src/database/mod.rs +++ /dev/null @@ -1,786 +0,0 @@ -mod config; -mod error; - -pub mod compat; -pub mod models; -pub mod schema; -pub mod utils; -pub mod watch_types; - -use self::schema::{ - active_config, beacon_blocks, canonical_slots, proposer_info, suboptimal_attestations, - validators, -}; - -use diesel::dsl::max; -use diesel::prelude::*; -use diesel::r2d2::{Builder, ConnectionManager, Pool, PooledConnection}; -use diesel::upsert::excluded; -use log::{debug, info}; -use std::collections::HashMap; -use std::time::Instant; -use types::{EthSpec, SignedBeaconBlock}; - -pub use self::error::Error; -pub use self::models::{WatchBeaconBlock, WatchCanonicalSlot, WatchProposerInfo, WatchValidator}; -pub use self::watch_types::{WatchHash, WatchPK, WatchSlot}; - -// Clippy has false positives on these re-exports from Rust 1.75.0-beta.1. -#[allow(unused_imports)] -pub use crate::block_rewards::{ - get_block_rewards_by_root, get_block_rewards_by_slot, get_highest_block_rewards, - get_lowest_block_rewards, get_unknown_block_rewards, insert_batch_block_rewards, - WatchBlockRewards, -}; - -#[allow(unused_imports)] -pub use crate::block_packing::{ - get_block_packing_by_root, get_block_packing_by_slot, get_highest_block_packing, - get_lowest_block_packing, get_unknown_block_packing, insert_batch_block_packing, - WatchBlockPacking, -}; - -#[allow(unused_imports)] -pub use crate::suboptimal_attestations::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, get_attestation_by_pubkey, - get_highest_attestation, get_lowest_attestation, insert_batch_suboptimal_attestations, - WatchAttestation, WatchSuboptimalAttestation, -}; - -#[allow(unused_imports)] -pub use crate::blockprint::{ - get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, - get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, - WatchBlockprint, -}; - -pub use config::Config; - -/// Batch inserts cannot exceed a certain size. -/// See https://github.com/diesel-rs/diesel/issues/2414. -/// For some reason, this seems to translate to 65535 / 5 (13107) records. -pub const MAX_SIZE_BATCH_INSERT: usize = 13107; - -pub type PgPool = Pool>; -pub type PgConn = PooledConnection>; - -/// Connect to a Postgresql database and build a connection pool. -pub fn build_connection_pool(config: &Config) -> Result { - let database_url = config.clone().build_database_url(); - info!("Building connection pool at: {database_url}"); - let pg = ConnectionManager::::new(&database_url); - Builder::new().build(pg).map_err(Error::Pool) -} - -/// Retrieve an idle connection from the pool. -pub fn get_connection(pool: &PgPool) -> Result { - pool.get().map_err(Error::Pool) -} - -/// Insert the active config into the database. This is used to check if the connected beacon node -/// is compatible with the database. These values will not change (except -/// `current_blockprint_checkpoint`). -pub fn insert_active_config( - conn: &mut PgConn, - new_config_name: String, - new_slots_per_epoch: u64, -) -> Result<(), Error> { - use self::active_config::dsl::*; - - diesel::insert_into(active_config) - .values(&vec![( - id.eq(1), - config_name.eq(new_config_name), - slots_per_epoch.eq(new_slots_per_epoch as i32), - )]) - .on_conflict_do_nothing() - .execute(conn)?; - - Ok(()) -} - -/// Get the active config from the database. -pub fn get_active_config(conn: &mut PgConn) -> Result, Error> { - use self::active_config::dsl::*; - Ok(active_config - .select((config_name, slots_per_epoch)) - .filter(id.eq(1)) - .first::<(String, i32)>(conn) - .optional()?) -} - -/* - * INSERT statements - */ - -/// Inserts a single row into the `canonical_slots` table. -/// If `new_slot.beacon_block` is `None`, the value in the row will be `null`. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_canonical_slot(conn: &mut PgConn, new_slot: WatchCanonicalSlot) -> Result<(), Error> { - diesel::insert_into(canonical_slots::table) - .values(&new_slot) - .on_conflict_do_nothing() - .execute(conn)?; - - debug!("Canonical slot inserted: {}", new_slot.slot); - Ok(()) -} - -pub fn insert_beacon_block( - conn: &mut PgConn, - block: SignedBeaconBlock, - root: WatchHash, -) -> Result<(), Error> { - use self::canonical_slots::dsl::{beacon_block, slot as canonical_slot}; - - let block_message = block.message(); - - // Pull out relevant values from the block. - let slot = WatchSlot::from_slot(block.slot()); - let parent_root = WatchHash::from_hash(block.parent_root()); - let proposer_index = block_message.proposer_index() as i32; - let graffiti = block_message.body().graffiti().as_utf8_lossy(); - let attestation_count = block_message.body().attestations_len() as i32; - - let full_payload = block_message.execution_payload().ok(); - - let transaction_count: Option = if let Some(bellatrix_payload) = - full_payload.and_then(|payload| payload.execution_payload_bellatrix().ok()) - { - Some(bellatrix_payload.transactions.len() as i32) - } else { - full_payload - .and_then(|payload| payload.execution_payload_capella().ok()) - .map(|payload| payload.transactions.len() as i32) - }; - - let withdrawal_count: Option = full_payload - .and_then(|payload| payload.execution_payload_capella().ok()) - .map(|payload| payload.withdrawals.len() as i32); - - let block_to_add = WatchBeaconBlock { - slot, - root, - parent_root, - attestation_count, - transaction_count, - withdrawal_count, - }; - - let proposer_info_to_add = WatchProposerInfo { - slot, - proposer_index, - graffiti, - }; - - // Update the canonical slots table. - diesel::update(canonical_slots::table) - .set(beacon_block.eq(root)) - .filter(canonical_slot.eq(slot)) - // Do not overwrite the value if it already exists. - .filter(beacon_block.is_null()) - .execute(conn)?; - - diesel::insert_into(beacon_blocks::table) - .values(block_to_add) - .on_conflict_do_nothing() - .execute(conn)?; - - diesel::insert_into(proposer_info::table) - .values(proposer_info_to_add) - .on_conflict_do_nothing() - .execute(conn)?; - - debug!("Beacon block inserted at slot: {slot}, root: {root}, parent: {parent_root}"); - Ok(()) -} - -/// Insert a validator into the `validators` table -/// -/// On a conflict, it will only overwrite `status`, `activation_epoch` and `exit_epoch`. -pub fn insert_validator(conn: &mut PgConn, validator: WatchValidator) -> Result<(), Error> { - use self::validators::dsl::*; - let new_index = validator.index; - let new_public_key = validator.public_key; - - diesel::insert_into(validators) - .values(validator) - .on_conflict(index) - .do_update() - .set(( - status.eq(excluded(status)), - activation_epoch.eq(excluded(activation_epoch)), - exit_epoch.eq(excluded(exit_epoch)), - )) - .execute(conn)?; - - debug!("Validator inserted, index: {new_index}, public_key: {new_public_key}"); - Ok(()) -} - -/// Insert a batch of values into the `validators` table. -/// -/// On a conflict, it will do nothing. -/// -/// Should not be used when updating validators. -/// Validators should be updated through the `insert_validator` function which contains the correct -/// `on_conflict` clauses. -pub fn insert_batch_validators( - conn: &mut PgConn, - all_validators: Vec, -) -> Result<(), Error> { - use self::validators::dsl::*; - - let mut count = 0; - - for chunk in all_validators.chunks(1000) { - count += diesel::insert_into(validators) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - debug!("Validators inserted, count: {count}"); - Ok(()) -} - -/* - * SELECT statements - */ - -/// Selects a single row of the `canonical_slots` table corresponding to a given `slot_query`. -pub fn get_canonical_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `canonical_slots` table corresponding to a given `root_query`. -/// Only returns the non-skipped slot which matches `root`. -pub fn get_canonical_slot_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(root.eq(root_query)) - .filter(skipped.eq(false)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical root requested: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `root` from a single row of the `canonical_slots` table corresponding to a given -/// `slot_query`. -#[allow(dead_code)] -pub fn get_root_at_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .select(root) - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from the row of the `canonical_slots` table corresponding to the minimum value -/// of `slot`. -pub fn get_lowest_canonical_slot(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: lowest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from the row of the `canonical_slots` table corresponding to the minimum value -/// of `slot` and where `skipped == false`. -pub fn get_lowest_non_skipped_canonical_slot( - conn: &mut PgConn, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(skipped.eq(false)) - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: lowest_non_skipped, time taken: {time_taken:?})"); - Ok(result) -} - -/// Select 'slot' from the row of the `canonical_slots` table corresponding to the maximum value -/// of `slot`. -pub fn get_highest_canonical_slot(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: highest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select 'slot' from the row of the `canonical_slots` table corresponding to the maximum value -/// of `slot` and where `skipped == false`. -pub fn get_highest_non_skipped_canonical_slot( - conn: &mut PgConn, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(skipped.eq(false)) - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: highest_non_skipped, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select all rows of the `canonical_slots` table where `slot >= `start_slot && slot <= -/// `end_slot`. -pub fn get_canonical_slots_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!( - "Canonical slots by range requested, start_slot: {}, end_slot: {}, time_taken: {:?}", - start_slot.as_u64(), - end_slot.as_u64(), - time_taken - ); - Ok(result) -} - -/// Selects `root` from all rows of the `canonical_slots` table which have `beacon_block == null` -/// and `skipped == false` -pub fn get_unknown_canonical_blocks(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - - let result = canonical_slots - .select(root) - .filter(beacon_block.is_null()) - .filter(skipped.eq(false)) - .order_by(slot.desc()) - .load::(conn)?; - - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `slot` is minimum. -pub fn get_lowest_beacon_block(conn: &mut PgConn) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon block requested: lowest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `slot` is maximum. -pub fn get_highest_beacon_block(conn: &mut PgConn) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon block requested: highest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `beacon_blocks` table corresponding to a given `root_query`. -pub fn get_beacon_block_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - let time_taken = timer.elapsed(); - debug!("Beacon block requested: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `beacon_blocks` table corresponding to a given `slot_query`. -pub fn get_beacon_block_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - let time_taken = timer.elapsed(); - debug!("Beacon block requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `parent_root` equals the given `parent`. -/// This fetches the next block in the database. -/// -/// Will return `Ok(None)` if there are no matching blocks (e.g. the tip of the chain). -pub fn get_beacon_block_with_parent( - conn: &mut PgConn, - parent: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(parent_root.eq(parent)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Next beacon block requested: {parent}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select all rows of the `beacon_blocks` table where `slot >= `start_slot && slot <= -/// `end_slot`. -pub fn get_beacon_blocks_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon blocks by range requested, start_slot: {start_slot}, end_slot: {end_slot}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `proposer_info` table corresponding to a given `root_query`. -pub fn get_proposer_info_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(proposer_info); - - let result = join - .select((slot, proposer_index, graffiti)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Proposer info requested for block: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `proposer_info` table corresponding to a given `slot_query`. -pub fn get_proposer_info_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let result = proposer_info - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Proposer info requested for slot: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects multiple rows of the `proposer_info` table between `start_slot` and `end_slot`. -/// Selects a single row of the `proposer_info` table corresponding to a given `slot_query`. -#[allow(dead_code)] -pub fn get_proposer_info_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let result = proposer_info - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!( - "Proposer info requested for range: {start_slot} to {end_slot}, time taken: {time_taken:?}" - ); - Ok(result) -} - -pub fn get_validators_latest_proposer_info( - conn: &mut PgConn, - indices_query: Vec, -) -> Result, Error> { - use self::proposer_info::dsl::*; - - let proposers = proposer_info - .filter(proposer_index.eq_any(indices_query)) - .load::(conn)?; - - let mut result = HashMap::new(); - for proposer in proposers { - result - .entry(proposer.proposer_index) - .or_insert_with(|| proposer.clone()); - let entry = result - .get_mut(&proposer.proposer_index) - .ok_or_else(|| Error::Other("An internal error occured".to_string()))?; - if proposer.slot > entry.slot { - entry.slot = proposer.slot - } - } - - Ok(result) -} - -/// Selects the max(`slot`) and `proposer_index` of each unique index in the -/// `proposer_info` table and returns them formatted as a `HashMap`. -/// Only returns rows which have `slot <= target_slot`. -/// -/// Ideally, this would return the full row, but I have not found a way to do that without using -/// a much more expensive SQL query. -pub fn get_all_validators_latest_proposer_info_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, -) -> Result, Error> { - use self::proposer_info::dsl::*; - - let latest_proposals: Vec<(i32, Option)> = proposer_info - .group_by(proposer_index) - .select((proposer_index, max(slot))) - .filter(slot.le(target_slot)) - .load::<(i32, Option)>(conn)?; - - let mut result = HashMap::new(); - - for proposal in latest_proposals { - if let Some(latest_slot) = proposal.1 { - result.insert(latest_slot, proposal.0); - } - } - - Ok(result) -} - -/// Selects a single row from the `validators` table corresponding to a given -/// `validator_index_query`. -pub fn get_validator_by_index( - conn: &mut PgConn, - validator_index_query: i32, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators - .filter(index.eq(validator_index_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Validator requested: {validator_index_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `validators` table corresponding to a given -/// `public_key_query`. -pub fn get_validator_by_public_key( - conn: &mut PgConn, - public_key_query: WatchPK, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators - .filter(public_key.eq(public_key_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Validator requested: {public_key_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects all rows from the `validators` table which have an `index` contained in -/// the `indices_query`. -#[allow(dead_code)] -pub fn get_validators_by_indices( - conn: &mut PgConn, - indices_query: Vec, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let query_len = indices_query.len(); - let result = validators - .filter(index.eq_any(indices_query)) - .load::(conn)?; - - let time_taken = timer.elapsed(); - debug!("{query_len} validators requested, time taken: {time_taken:?}"); - Ok(result) -} - -// Selects all rows from the `validators` table. -pub fn get_all_validators(conn: &mut PgConn) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators.load::(conn)?; - - let time_taken = timer.elapsed(); - debug!("All validators requested, time taken: {time_taken:?}"); - Ok(result) -} - -/// Counts the number of rows in the `validators` table. -#[allow(dead_code)] -pub fn count_validators(conn: &mut PgConn) -> Result { - use self::validators::dsl::*; - - validators.count().get_result(conn).map_err(Error::Database) -} - -/// Counts the number of rows in the `validators` table where -/// `activation_epoch <= target_slot.epoch()`. -pub fn count_validators_activated_before_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result { - use self::validators::dsl::*; - - let target_epoch = target_slot.epoch(slots_per_epoch); - - validators - .count() - .filter(activation_epoch.le(target_epoch.as_u64() as i32)) - .get_result(conn) - .map_err(Error::Database) -} - -/* - * DELETE statements. - */ - -/// Deletes all rows of the `canonical_slots` table which have `slot` greater than `slot_query`. -/// -/// Due to the ON DELETE CASCADE clause present in the database migration SQL, deleting rows from -/// `canonical_slots` will delete all corresponding rows in `beacon_blocks, `block_rewards`, -/// `block_packing` and `proposer_info`. -pub fn delete_canonical_slots_above( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result { - use self::canonical_slots::dsl::*; - - let result = diesel::delete(canonical_slots) - .filter(slot.gt(slot_query)) - .execute(conn)?; - - debug!("Deleted canonical slots above {slot_query}: {result} rows deleted"); - Ok(result) -} - -/// Deletes all rows of the `suboptimal_attestations` table which have `epoch_start_slot` greater -/// than `epoch_start_slot_query`. -pub fn delete_suboptimal_attestations_above( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result { - use self::suboptimal_attestations::dsl::*; - - let result = diesel::delete(suboptimal_attestations) - .filter(epoch_start_slot.gt(epoch_start_slot_query)) - .execute(conn)?; - - debug!("Deleted attestations above: {epoch_start_slot_query}, rows deleted: {result}"); - Ok(result) -} diff --git a/watch/src/database/models.rs b/watch/src/database/models.rs deleted file mode 100644 index f42444d661..0000000000 --- a/watch/src/database/models.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, canonical_slots, proposer_info, validators}, - watch_types::{WatchHash, WatchPK, WatchSlot}, -}; -use diesel::{Insertable, Queryable}; -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; - -pub type WatchEpoch = i32; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = canonical_slots)] -pub struct WatchCanonicalSlot { - pub slot: WatchSlot, - pub root: WatchHash, - pub skipped: bool, - pub beacon_block: Option, -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = beacon_blocks)] -pub struct WatchBeaconBlock { - pub slot: WatchSlot, - pub root: WatchHash, - pub parent_root: WatchHash, - pub attestation_count: i32, - pub transaction_count: Option, - pub withdrawal_count: Option, -} - -#[derive(Clone, Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = validators)] -pub struct WatchValidator { - pub index: i32, - pub public_key: WatchPK, - pub status: String, - pub activation_epoch: Option, - pub exit_epoch: Option, -} - -// Implement a minimal version of `Hash` and `Eq` so that we know if a validator status has changed. -impl Hash for WatchValidator { - fn hash(&self, state: &mut H) { - self.index.hash(state); - self.status.hash(state); - self.activation_epoch.hash(state); - self.exit_epoch.hash(state); - } -} - -impl PartialEq for WatchValidator { - fn eq(&self, other: &Self) -> bool { - self.index == other.index - && self.status == other.status - && self.activation_epoch == other.activation_epoch - && self.exit_epoch == other.exit_epoch - } -} -impl Eq for WatchValidator {} - -#[derive(Clone, Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = proposer_info)] -pub struct WatchProposerInfo { - pub slot: WatchSlot, - pub proposer_index: i32, - pub graffiti: String, -} diff --git a/watch/src/database/schema.rs b/watch/src/database/schema.rs deleted file mode 100644 index 32f22d506d..0000000000 --- a/watch/src/database/schema.rs +++ /dev/null @@ -1,102 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - active_config (id) { - id -> Int4, - config_name -> Text, - slots_per_epoch -> Int4, - } -} - -diesel::table! { - beacon_blocks (slot) { - slot -> Int4, - root -> Bytea, - parent_root -> Bytea, - attestation_count -> Int4, - transaction_count -> Nullable, - withdrawal_count -> Nullable, - } -} - -diesel::table! { - block_packing (slot) { - slot -> Int4, - available -> Int4, - included -> Int4, - prior_skip_slots -> Int4, - } -} - -diesel::table! { - block_rewards (slot) { - slot -> Int4, - total -> Int4, - attestation_reward -> Int4, - sync_committee_reward -> Int4, - } -} - -diesel::table! { - blockprint (slot) { - slot -> Int4, - best_guess -> Text, - } -} - -diesel::table! { - canonical_slots (slot) { - slot -> Int4, - root -> Bytea, - skipped -> Bool, - beacon_block -> Nullable, - } -} - -diesel::table! { - proposer_info (slot) { - slot -> Int4, - proposer_index -> Int4, - graffiti -> Text, - } -} - -diesel::table! { - suboptimal_attestations (epoch_start_slot, index) { - epoch_start_slot -> Int4, - index -> Int4, - source -> Bool, - head -> Bool, - target -> Bool, - } -} - -diesel::table! { - validators (index) { - index -> Int4, - public_key -> Bytea, - status -> Text, - activation_epoch -> Nullable, - exit_epoch -> Nullable, - } -} - -diesel::joinable!(block_packing -> beacon_blocks (slot)); -diesel::joinable!(block_rewards -> beacon_blocks (slot)); -diesel::joinable!(blockprint -> beacon_blocks (slot)); -diesel::joinable!(proposer_info -> beacon_blocks (slot)); -diesel::joinable!(proposer_info -> validators (proposer_index)); -diesel::joinable!(suboptimal_attestations -> canonical_slots (epoch_start_slot)); -diesel::joinable!(suboptimal_attestations -> validators (index)); - -diesel::allow_tables_to_appear_in_same_query!( - active_config, - beacon_blocks, - block_packing, - block_rewards, - blockprint, - canonical_slots, - proposer_info, - suboptimal_attestations, - validators, -); diff --git a/watch/src/database/utils.rs b/watch/src/database/utils.rs deleted file mode 100644 index 9134c3698f..0000000000 --- a/watch/src/database/utils.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![allow(dead_code)] -use crate::database::config::Config; -use diesel::prelude::*; -use diesel_migrations::{FileBasedMigrations, MigrationHarness}; - -/// Sets `config.dbname` to `config.default_dbname` and returns `(new_config, old_dbname)`. -/// -/// This is useful for creating or dropping databases, since these actions must be done by -/// logging into another database. -pub fn get_config_using_default_db(config: &Config) -> (Config, String) { - let mut config = config.clone(); - let new_dbname = std::mem::replace(&mut config.dbname, config.default_dbname.clone()); - (config, new_dbname) -} - -/// Runs the set of migrations as detected in the local directory. -/// Equivalent to `diesel migration run`. -/// -/// Contains `unwrap`s so is only suitable for test code. -/// TODO(mac) refactor to return Result -pub fn run_migrations(config: &Config) -> PgConnection { - let database_url = config.clone().build_database_url(); - let mut conn = PgConnection::establish(&database_url).unwrap(); - let migrations = FileBasedMigrations::find_migrations_directory().unwrap(); - conn.run_pending_migrations(migrations).unwrap(); - conn.begin_test_transaction().unwrap(); - conn -} diff --git a/watch/src/database/watch_types.rs b/watch/src/database/watch_types.rs deleted file mode 100644 index c2b67084c9..0000000000 --- a/watch/src/database/watch_types.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::database::error::Error; -use diesel::{ - sql_types::{Binary, Integer}, - AsExpression, FromSqlRow, -}; -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::str::FromStr; -use types::{Epoch, Hash256, PublicKeyBytes, Slot}; -#[derive( - Clone, - Copy, - Debug, - AsExpression, - FromSqlRow, - Deserialize, - Serialize, - Hash, - PartialEq, - Eq, - PartialOrd, - Ord, -)] -#[diesel(sql_type = Integer)] -pub struct WatchSlot(Slot); - -impl fmt::Display for WatchSlot { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl WatchSlot { - pub fn new(slot: u64) -> Self { - Self(Slot::new(slot)) - } - - pub fn from_slot(slot: Slot) -> Self { - Self(slot) - } - - pub fn as_slot(self) -> Slot { - self.0 - } - - pub fn as_u64(self) -> u64 { - self.0.as_u64() - } - - pub fn epoch(self, slots_per_epoch: u64) -> Epoch { - self.as_slot().epoch(slots_per_epoch) - } -} - -#[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, Deserialize, Serialize)] -#[diesel(sql_type = Binary)] -pub struct WatchHash(Hash256); - -impl fmt::Display for WatchHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl WatchHash { - pub fn as_hash(&self) -> Hash256 { - self.0 - } - - pub fn from_hash(hash: Hash256) -> Self { - WatchHash(hash) - } - - pub fn as_bytes(&self) -> &[u8] { - self.0.as_slice() - } - - pub fn from_bytes(src: &[u8]) -> Result { - if src.len() == 32 { - Ok(WatchHash(Hash256::from_slice(src))) - } else { - Err(Error::InvalidRoot) - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, AsExpression, FromSqlRow, Serialize, Deserialize)] -#[diesel(sql_type = Binary)] -pub struct WatchPK(PublicKeyBytes); - -impl fmt::Display for WatchPK { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl WatchPK { - pub fn as_bytes(&self) -> &[u8] { - self.0.as_serialized() - } - - pub fn from_bytes(src: &[u8]) -> Result { - Ok(WatchPK(PublicKeyBytes::deserialize(src)?)) - } - - pub fn from_pubkey(key: PublicKeyBytes) -> Self { - WatchPK(key) - } -} - -impl FromStr for WatchPK { - type Err = String; - - fn from_str(s: &str) -> Result { - Ok(WatchPK( - PublicKeyBytes::from_str(s).map_err(|e| format!("Cannot be parsed: {}", e))?, - )) - } -} diff --git a/watch/src/lib.rs b/watch/src/lib.rs deleted file mode 100644 index 664c945165..0000000000 --- a/watch/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(unix)] -pub mod block_packing; -pub mod block_rewards; -pub mod blockprint; -pub mod cli; -pub mod client; -pub mod config; -pub mod database; -pub mod logger; -pub mod server; -pub mod suboptimal_attestations; -pub mod updater; diff --git a/watch/src/logger.rs b/watch/src/logger.rs deleted file mode 100644 index 49310b42aa..0000000000 --- a/watch/src/logger.rs +++ /dev/null @@ -1,24 +0,0 @@ -use env_logger::Builder; -use log::{info, LevelFilter}; -use std::process; - -pub fn init_logger(log_level: &str) { - let log_level = match log_level.to_lowercase().as_str() { - "trace" => LevelFilter::Trace, - "debug" => LevelFilter::Debug, - "info" => LevelFilter::Info, - "warn" => LevelFilter::Warn, - "error" => LevelFilter::Error, - _ => { - eprintln!("Unsupported log level"); - process::exit(1) - } - }; - - let mut builder = Builder::new(); - builder.filter(Some("watch"), log_level); - - builder.init(); - - info!("Logger initialized with log-level: {log_level}"); -} diff --git a/watch/src/main.rs b/watch/src/main.rs deleted file mode 100644 index f971747da4..0000000000 --- a/watch/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[cfg(unix)] -use std::process; - -#[cfg(unix)] -mod block_packing; -#[cfg(unix)] -mod block_rewards; -#[cfg(unix)] -mod blockprint; -#[cfg(unix)] -mod cli; -#[cfg(unix)] -mod config; -#[cfg(unix)] -mod database; -#[cfg(unix)] -mod logger; -#[cfg(unix)] -mod server; -#[cfg(unix)] -mod suboptimal_attestations; -#[cfg(unix)] -mod updater; - -#[cfg(unix)] -#[tokio::main] -async fn main() { - match cli::run().await { - Ok(()) => process::exit(0), - Err(e) => { - eprintln!("Command failed with: {}", e); - drop(e); - process::exit(1) - } - } -} - -#[cfg(windows)] -fn main() { - eprintln!("Windows is not supported. Exiting."); -} diff --git a/watch/src/server/config.rs b/watch/src/server/config.rs deleted file mode 100644 index a7d38e706f..0000000000 --- a/watch/src/server/config.rs +++ /dev/null @@ -1,28 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; - -pub const LISTEN_ADDR: &str = "127.0.0.1"; - -pub const fn listen_port() -> u16 { - 5059 -} -fn listen_addr() -> IpAddr { - LISTEN_ADDR.parse().expect("Server address is not valid") -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "listen_addr")] - pub listen_addr: IpAddr, - #[serde(default = "listen_port")] - pub listen_port: u16, -} - -impl Default for Config { - fn default() -> Self { - Self { - listen_addr: listen_addr(), - listen_port: listen_port(), - } - } -} diff --git a/watch/src/server/error.rs b/watch/src/server/error.rs deleted file mode 100644 index e2c8f0f42a..0000000000 --- a/watch/src/server/error.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::database::Error as DbError; -use axum::Error as AxumError; -use axum::{http::StatusCode, response::IntoResponse, Json}; -use hyper::Error as HyperError; -use serde_json::json; -use std::io::Error as IoError; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - Axum(AxumError), - Hyper(HyperError), - Database(DbError), - IoError(IoError), - BadRequest, - NotFound, - Other(String), -} - -impl IntoResponse for Error { - fn into_response(self) -> axum::response::Response { - let (status, error_message) = match self { - Self::BadRequest => (StatusCode::BAD_REQUEST, "Bad Request"), - Self::NotFound => (StatusCode::NOT_FOUND, "Not Found"), - _ => (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error"), - }; - (status, Json(json!({ "error": error_message }))).into_response() - } -} - -impl From for Error { - fn from(e: HyperError) -> Self { - Error::Hyper(e) - } -} - -impl From for Error { - fn from(e: AxumError) -> Self { - Error::Axum(e) - } -} - -impl From for Error { - fn from(e: DbError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: IoError) -> Self { - Error::IoError(e) - } -} - -impl From for Error { - fn from(e: String) -> Self { - Error::Other(e) - } -} diff --git a/watch/src/server/handler.rs b/watch/src/server/handler.rs deleted file mode 100644 index 6777026867..0000000000 --- a/watch/src/server/handler.rs +++ /dev/null @@ -1,266 +0,0 @@ -use crate::database::{ - self, Error as DbError, PgPool, WatchBeaconBlock, WatchCanonicalSlot, WatchHash, WatchPK, - WatchProposerInfo, WatchSlot, WatchValidator, -}; -use crate::server::Error; -use axum::{ - extract::{Path, Query}, - Extension, Json, -}; -use eth2::types::BlockId; -use std::collections::HashMap; -use std::str::FromStr; - -pub async fn get_slot( - Path(slot): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_canonical_slot( - &mut conn, - WatchSlot::new(slot), - )?)) -} - -pub async fn get_slot_lowest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_lowest_canonical_slot(&mut conn)?)) -} - -pub async fn get_slot_highest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_highest_canonical_slot(&mut conn)?)) -} - -pub async fn get_slots_by_range( - Query(query): Query>, - Extension(pool): Extension, -) -> Result>>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if let Some(start_slot) = query.get("start_slot") { - if let Some(end_slot) = query.get("end_slot") { - if start_slot > end_slot { - Err(Error::BadRequest) - } else { - Ok(Json(database::get_canonical_slots_by_range( - &mut conn, - WatchSlot::new(*start_slot), - WatchSlot::new(*end_slot), - )?)) - } - } else { - Err(Error::BadRequest) - } - } else { - Err(Error::BadRequest) - } -} - -pub async fn get_block( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - let block_id: BlockId = BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)?; - match block_id { - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - BlockId::Root(root) => Ok(Json(database::get_beacon_block_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_block_lowest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_lowest_beacon_block(&mut conn)?)) -} - -pub async fn get_block_highest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_highest_beacon_block(&mut conn)?)) -} - -pub async fn get_block_previous( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => { - if let Some(block) = - database::get_beacon_block_by_root(&mut conn, WatchHash::from_hash(root))? - .map(|block| block.parent_root) - { - Ok(Json(database::get_beacon_block_by_root(&mut conn, block)?)) - } else { - Err(Error::NotFound) - } - } - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::new(slot.as_u64().checked_sub(1_u64).ok_or(Error::NotFound)?), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_block_next( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(database::get_beacon_block_with_parent( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::from_slot(slot + 1_u64), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_blocks_by_range( - Query(query): Query>, - Extension(pool): Extension, -) -> Result>>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if let Some(start_slot) = query.get("start_slot") { - if let Some(end_slot) = query.get("end_slot") { - if start_slot > end_slot { - Err(Error::BadRequest) - } else { - Ok(Json(database::get_beacon_blocks_by_range( - &mut conn, - WatchSlot::new(*start_slot), - WatchSlot::new(*end_slot), - )?)) - } - } else { - Err(Error::BadRequest) - } - } else { - Err(Error::BadRequest) - } -} - -pub async fn get_block_proposer( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(database::get_proposer_info_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(database::get_proposer_info_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_validator( - Path(validator_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validator_by_public_key( - &mut conn, pubkey, - )?)) - } else { - let index = i32::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validator_by_index(&mut conn, index)?)) - } -} - -pub async fn get_all_validators( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_all_validators(&mut conn)?)) -} - -pub async fn get_validator_latest_proposal( - Path(validator_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - let validator = - database::get_validator_by_public_key(&mut conn, pubkey)?.ok_or(Error::NotFound)?; - Ok(Json(database::get_validators_latest_proposer_info( - &mut conn, - vec![validator.index], - )?)) - } else { - let index = i32::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validators_latest_proposer_info( - &mut conn, - vec![index], - )?)) - } -} - -pub async fn get_client_breakdown( - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - - if let Some(target_slot) = database::get_highest_canonical_slot(&mut conn)? { - Ok(Json(database::get_validators_clients_at_slot( - &mut conn, - target_slot.slot, - slots_per_epoch, - )?)) - } else { - Err(Error::Database(DbError::Other( - "No slots found in database.".to_string(), - ))) - } -} - -pub async fn get_client_breakdown_percentages( - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - - let mut result = HashMap::new(); - if let Some(target_slot) = database::get_highest_canonical_slot(&mut conn)? { - let total = database::count_validators_activated_before_slot( - &mut conn, - target_slot.slot, - slots_per_epoch, - )?; - let clients = - database::get_validators_clients_at_slot(&mut conn, target_slot.slot, slots_per_epoch)?; - for (client, number) in clients.iter() { - let percentage: f64 = *number as f64 / total as f64 * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} diff --git a/watch/src/server/mod.rs b/watch/src/server/mod.rs deleted file mode 100644 index 08036db951..0000000000 --- a/watch/src/server/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::block_packing::block_packing_routes; -use crate::block_rewards::block_rewards_routes; -use crate::blockprint::blockprint_routes; -use crate::config::Config as FullConfig; -use crate::database::{self, PgPool}; -use crate::suboptimal_attestations::{attestation_routes, blockprint_attestation_routes}; -use axum::{ - http::{StatusCode, Uri}, - routing::get, - Extension, Json, Router, -}; -use eth2::types::ErrorMessage; -use log::info; -use std::future::{Future, IntoFuture}; -use std::net::{SocketAddr, TcpListener}; - -pub use config::Config; -pub use error::Error; - -mod config; -mod error; -mod handler; - -pub async fn serve(config: FullConfig) -> Result<(), Error> { - let db = database::build_connection_pool(&config.database)?; - let (_, slots_per_epoch) = database::get_active_config(&mut database::get_connection(&db)?)? - .ok_or_else(|| { - Error::Other( - "Database not found. Please run the updater prior to starting the server" - .to_string(), - ) - })?; - - let (_addr, server) = start_server(&config, slots_per_epoch as u64, db)?; - - server.await?; - - Ok(()) -} - -/// Creates a server that will serve requests using information from `config`. -/// -/// The server will create its own connection pool to serve connections to the database. -/// This is separate to the connection pool that is used for the `updater`. -/// -/// The server will shut down gracefully when the `shutdown` future resolves. -/// -/// ## Returns -/// -/// This function will bind the server to the address specified in the config and then return a -/// Future representing the actual server that will need to be awaited. -/// -/// ## Errors -/// -/// Returns an error if the server is unable to bind or there is another error during -/// configuration. -pub fn start_server( - config: &FullConfig, - slots_per_epoch: u64, - pool: PgPool, -) -> Result< - ( - SocketAddr, - impl Future> + 'static, - ), - Error, -> { - let mut routes = Router::new() - .route("/v1/slots", get(handler::get_slots_by_range)) - .route("/v1/slots/:slot", get(handler::get_slot)) - .route("/v1/slots/lowest", get(handler::get_slot_lowest)) - .route("/v1/slots/highest", get(handler::get_slot_highest)) - .route("/v1/slots/:slot/block", get(handler::get_block)) - .route("/v1/blocks", get(handler::get_blocks_by_range)) - .route("/v1/blocks/:block", get(handler::get_block)) - .route("/v1/blocks/lowest", get(handler::get_block_lowest)) - .route("/v1/blocks/highest", get(handler::get_block_highest)) - .route( - "/v1/blocks/:block/previous", - get(handler::get_block_previous), - ) - .route("/v1/blocks/:block/next", get(handler::get_block_next)) - .route( - "/v1/blocks/:block/proposer", - get(handler::get_block_proposer), - ) - .route("/v1/validators/:validator", get(handler::get_validator)) - .route("/v1/validators/all", get(handler::get_all_validators)) - .route( - "/v1/validators/:validator/latest_proposal", - get(handler::get_validator_latest_proposal), - ) - .route("/v1/clients", get(handler::get_client_breakdown)) - .route( - "/v1/clients/percentages", - get(handler::get_client_breakdown_percentages), - ) - .merge(attestation_routes()) - .merge(blockprint_routes()) - .merge(block_packing_routes()) - .merge(block_rewards_routes()); - - if config.blockprint.enabled && config.updater.attestations { - routes = routes.merge(blockprint_attestation_routes()) - } - - let app = routes - .fallback(route_not_found) - .layer(Extension(pool)) - .layer(Extension(slots_per_epoch)); - - let addr = SocketAddr::new(config.server.listen_addr, config.server.listen_port); - let listener = TcpListener::bind(addr)?; - listener.set_nonblocking(true)?; - - // Read the socket address (it may be different from `addr` if listening on port 0). - let socket_addr = listener.local_addr()?; - - let serve = axum::serve(tokio::net::TcpListener::from_std(listener)?, app); - - info!("HTTP server listening on {}", addr); - - Ok((socket_addr, serve.into_future())) -} - -// The default route indicating that no available routes matched the request. -async fn route_not_found(uri: Uri) -> (StatusCode, Json) { - ( - StatusCode::METHOD_NOT_ALLOWED, - Json(ErrorMessage { - code: StatusCode::METHOD_NOT_ALLOWED.as_u16(), - message: format!("No route for {uri}"), - stacktraces: vec![], - }), - ) -} diff --git a/watch/src/suboptimal_attestations/database.rs b/watch/src/suboptimal_attestations/database.rs deleted file mode 100644 index cb947d250a..0000000000 --- a/watch/src/suboptimal_attestations/database.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::database::{ - schema::{suboptimal_attestations, validators}, - watch_types::{WatchPK, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -use types::Epoch; - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct WatchAttestation { - pub index: i32, - pub epoch: Epoch, - pub source: bool, - pub head: bool, - pub target: bool, -} - -impl WatchAttestation { - pub fn optimal(index: i32, epoch: Epoch) -> WatchAttestation { - WatchAttestation { - index, - epoch, - source: true, - head: true, - target: true, - } - } -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = suboptimal_attestations)] -pub struct WatchSuboptimalAttestation { - pub epoch_start_slot: WatchSlot, - pub index: i32, - pub source: bool, - pub head: bool, - pub target: bool, -} - -impl WatchSuboptimalAttestation { - pub fn to_attestation(&self, slots_per_epoch: u64) -> WatchAttestation { - WatchAttestation { - index: self.index, - epoch: self.epoch_start_slot.epoch(slots_per_epoch), - source: self.source, - head: self.head, - target: self.target, - } - } -} - -/// Insert a batch of values into the `suboptimal_attestations` table -/// -/// Since attestations technically occur per-slot but we only store them per-epoch (via its -/// `start_slot`) so if any slot in the epoch changes, we need to resync the whole epoch as a -/// 'suboptimal' attestation could now be 'optimal'. -/// -/// This is handled in the update code, where in the case of a re-org, the affected epoch is -/// deleted completely. -/// -/// On a conflict, it will do nothing. -pub fn insert_batch_suboptimal_attestations( - conn: &mut PgConn, - attestations: Vec, -) -> Result<(), Error> { - use self::suboptimal_attestations::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in attestations.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(suboptimal_attestations) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Attestations inserted, count: {count}, time taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `suboptimal_attestations` table where `epoch_start_slot` is minimum. -pub fn get_lowest_attestation( - conn: &mut PgConn, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .order_by(epoch_start_slot.asc()) - .limit(1) - .first::(conn) - .optional()?) -} - -/// Selects the row from the `suboptimal_attestations` table where `epoch_start_slot` is maximum. -pub fn get_highest_attestation( - conn: &mut PgConn, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .order_by(epoch_start_slot.desc()) - .limit(1) - .first::(conn) - .optional()?) -} - -/// Selects a single row from the `suboptimal_attestations` table corresponding to a given -/// `index_query` and `epoch_query`. -pub fn get_attestation_by_index( - conn: &mut PgConn, - index_query: i32, - epoch_query: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - let timer = Instant::now(); - - let result = suboptimal_attestations - .filter(epoch_start_slot.eq(WatchSlot::from_slot( - epoch_query.start_slot(slots_per_epoch), - ))) - .filter(index.eq(index_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Attestation requested for validator: {index_query}, epoch: {epoch_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `suboptimal_attestations` table corresponding -/// to a given `pubkey_query` and `epoch_query`. -#[allow(dead_code)] -pub fn get_attestation_by_pubkey( - conn: &mut PgConn, - pubkey_query: WatchPK, - epoch_query: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - use self::validators::dsl::{public_key, validators}; - let timer = Instant::now(); - - let join = validators.inner_join(suboptimal_attestations); - - let result = join - .select((epoch_start_slot, index, source, head, target)) - .filter(epoch_start_slot.eq(WatchSlot::from_slot( - epoch_query.start_slot(slots_per_epoch), - ))) - .filter(public_key.eq(pubkey_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Attestation requested for validator: {pubkey_query}, epoch: {epoch_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `source == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_source( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(source.eq(false)) - .load::(conn)?) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `head == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_head( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(head.eq(false)) - .load::(conn)?) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `target == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_target( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(target.eq(false)) - .load::(conn)?) -} - -/// Selects all rows from the `suboptimal_attestations` table for the given -/// `epoch_start_slot_query`. -pub fn get_all_suboptimal_attestations_for_epoch( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .load::(conn)?) -} diff --git a/watch/src/suboptimal_attestations/mod.rs b/watch/src/suboptimal_attestations/mod.rs deleted file mode 100644 index a94532e8ab..0000000000 --- a/watch/src/suboptimal_attestations/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, get_attestation_by_pubkey, - get_highest_attestation, get_lowest_attestation, insert_batch_suboptimal_attestations, - WatchAttestation, WatchSuboptimalAttestation, -}; - -pub use server::{attestation_routes, blockprint_attestation_routes}; - -use eth2::BeaconNodeHttpClient; -use types::Epoch; - -/// Sends a request to `lighthouse/analysis/attestation_performance`. -/// Formats the response into a vector of `WatchSuboptimalAttestation`. -/// -/// Any attestations with `source == true && head == true && target == true` are ignored. -pub async fn get_attestation_performances( - bn: &BeaconNodeHttpClient, - start_epoch: Epoch, - end_epoch: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - let mut output = Vec::new(); - let result = bn - .get_lighthouse_analysis_attestation_performance( - start_epoch, - end_epoch, - "global".to_string(), - ) - .await?; - for index in result { - for epoch in index.epochs { - if epoch.1.active { - // Check if the attestation is suboptimal. - if !epoch.1.source || !epoch.1.head || !epoch.1.target { - output.push(WatchSuboptimalAttestation { - epoch_start_slot: WatchSlot::from_slot( - Epoch::new(epoch.0).start_slot(slots_per_epoch), - ), - index: index.index as i32, - source: epoch.1.source, - head: epoch.1.head, - target: epoch.1.target, - }) - } - } - } - } - Ok(output) -} diff --git a/watch/src/suboptimal_attestations/server.rs b/watch/src/suboptimal_attestations/server.rs deleted file mode 100644 index 391db9a41b..0000000000 --- a/watch/src/suboptimal_attestations/server.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::database::{ - get_canonical_slot, get_connection, get_validator_by_index, get_validator_by_public_key, - get_validators_clients_at_slot, get_validators_latest_proposer_info, PgPool, WatchPK, - WatchSlot, -}; - -use crate::blockprint::database::construct_validator_blockprints_at_slot; -use crate::server::Error; -use crate::suboptimal_attestations::database::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, - get_validators_missed_head, get_validators_missed_source, get_validators_missed_target, - WatchAttestation, WatchSuboptimalAttestation, -}; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use std::collections::{HashMap, HashSet}; -use std::str::FromStr; -use types::Epoch; - -// Will return Ok(None) if the epoch is not synced or if the validator does not exist. -// In the future it might be worth differentiating these events. -pub async fn get_validator_attestation( - Path((validator_query, epoch_query)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - let epoch = Epoch::new(epoch_query); - - // Ensure the database has synced the target epoch. - if get_canonical_slot( - &mut conn, - WatchSlot::from_slot(epoch.end_slot(slots_per_epoch)), - )? - .is_none() - { - // Epoch is not fully synced. - return Ok(Json(None)); - } - - let index = if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - get_validator_by_public_key(&mut conn, pubkey)? - .ok_or(Error::NotFound)? - .index - } else { - i32::from_str(&validator_query).map_err(|_| Error::BadRequest)? - }; - let attestation = if let Some(suboptimal_attestation) = - get_attestation_by_index(&mut conn, index, epoch, slots_per_epoch)? - { - Some(suboptimal_attestation.to_attestation(slots_per_epoch)) - } else { - // Attestation was not in database. Check if the validator was active. - match get_validator_by_index(&mut conn, index)? { - Some(validator) => { - if let Some(activation_epoch) = validator.activation_epoch { - if activation_epoch <= epoch.as_u64() as i32 { - if let Some(exit_epoch) = validator.exit_epoch { - if exit_epoch > epoch.as_u64() as i32 { - // Validator is active and has not yet exited. - Some(WatchAttestation::optimal(index, epoch)) - } else { - // Validator has exited. - None - } - } else { - // Validator is active and has not yet exited. - Some(WatchAttestation::optimal(index, epoch)) - } - } else { - // Validator is not yet active. - None - } - } else { - // Validator is not yet active. - None - } - } - None => return Err(Error::Other("Validator index does not exist".to_string())), - } - }; - Ok(Json(attestation)) -} - -pub async fn get_all_validators_attestations( - Path(epoch): Path, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let epoch_start_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - Ok(Json(get_all_suboptimal_attestations_for_epoch( - &mut conn, - epoch_start_slot, - )?)) -} - -pub async fn get_validators_missed_vote( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let epoch_start_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - match vote.to_lowercase().as_str() { - "source" => Ok(Json(get_validators_missed_source( - &mut conn, - epoch_start_slot, - )?)), - "head" => Ok(Json(get_validators_missed_head( - &mut conn, - epoch_start_slot, - )?)), - "target" => Ok(Json(get_validators_missed_target( - &mut conn, - epoch_start_slot, - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_validators_missed_vote_graffiti( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let Json(indices) = get_validators_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - let graffitis = get_validators_latest_proposer_info(&mut conn, indices)? - .values() - .map(|info| info.graffiti.clone()) - .collect::>(); - - let mut result = HashMap::new(); - for graffiti in graffitis { - if !result.contains_key(&graffiti) { - result.insert(graffiti.clone(), 0); - } - *result - .get_mut(&graffiti) - .ok_or_else(|| Error::Other("An unexpected error occurred".to_string()))? += 1; - } - - Ok(Json(result)) -} - -pub fn attestation_routes() -> Router { - Router::new() - .route( - "/v1/validators/:validator/attestation/:epoch", - get(get_validator_attestation), - ) - .route( - "/v1/validators/all/attestation/:epoch", - get(get_all_validators_attestations), - ) - .route( - "/v1/validators/missed/:vote/:epoch", - get(get_validators_missed_vote), - ) - .route( - "/v1/validators/missed/:vote/:epoch/graffiti", - get(get_validators_missed_vote_graffiti), - ) -} - -/// The functions below are dependent on Blockprint and if it is disabled, the endpoints will be -/// disabled. -pub async fn get_clients_missed_vote( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let Json(indices) = get_validators_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - // All validators which missed the vote. - let indices_map = indices.into_iter().collect::>(); - - let target_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - // All validators. - let client_map = - construct_validator_blockprints_at_slot(&mut conn, target_slot, slots_per_epoch)?; - - let mut result = HashMap::new(); - - for index in indices_map { - if let Some(print) = client_map.get(&index) { - if !result.contains_key(print) { - result.insert(print.clone(), 0); - } - *result - .get_mut(print) - .ok_or_else(|| Error::Other("An unexpected error occurred".to_string()))? += 1; - } - } - - Ok(Json(result)) -} - -pub async fn get_clients_missed_vote_percentages( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let Json(clients_counts) = get_clients_missed_vote( - Path((vote, epoch)), - Extension(pool.clone()), - Extension(slots_per_epoch), - ) - .await?; - - let target_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - let mut conn = get_connection(&pool)?; - let totals = get_validators_clients_at_slot(&mut conn, target_slot, slots_per_epoch)?; - - let mut result = HashMap::new(); - for (client, count) in clients_counts.iter() { - let client_total: f64 = *totals - .get(client) - .ok_or_else(|| Error::Other("Client type mismatch".to_string()))? - as f64; - // `client_total` should never be `0`, but if it is, return `0` instead of `inf`. - if client_total == 0.0 { - result.insert(client.to_string(), 0.0); - } else { - let percentage: f64 = *count as f64 / client_total * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} - -pub async fn get_clients_missed_vote_percentages_relative( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let Json(clients_counts) = get_clients_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - let mut total: u64 = 0; - for (_, count) in clients_counts.iter() { - total += *count - } - - let mut result = HashMap::new(); - for (client, count) in clients_counts.iter() { - // `total` should never be 0, but if it is, return `-` instead of `inf`. - if total == 0 { - result.insert(client.to_string(), 0.0); - } else { - let percentage: f64 = *count as f64 / total as f64 * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} - -pub fn blockprint_attestation_routes() -> Router { - Router::new() - .route( - "/v1/clients/missed/:vote/:epoch", - get(get_clients_missed_vote), - ) - .route( - "/v1/clients/missed/:vote/:epoch/percentages", - get(get_clients_missed_vote_percentages), - ) - .route( - "/v1/clients/missed/:vote/:epoch/percentages/relative", - get(get_clients_missed_vote_percentages_relative), - ) -} diff --git a/watch/src/suboptimal_attestations/updater.rs b/watch/src/suboptimal_attestations/updater.rs deleted file mode 100644 index d8f6ec57d5..0000000000 --- a/watch/src/suboptimal_attestations/updater.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::suboptimal_attestations::get_attestation_performances; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS: u64 = 50; - -impl UpdateHandler { - /// Forward fills the `suboptimal_attestations` table starting from the entry with the highest - /// slot. - /// - /// It construts a request to the `attestation_performance` API endpoint with: - /// `start_epoch` -> highest completely filled epoch + 1 (or epoch of lowest canonical slot) - /// `end_epoch` -> epoch of highest canonical slot - /// - /// It will resync the latest epoch if it is not fully filled but will not overwrite existing - /// values unless there is a re-org. - /// That is, `if highest_filled_slot % slots_per_epoch != 31`. - /// - /// In the event the most recent epoch has no suboptimal attestations, it will attempt to - /// resync that epoch. The odds of this occuring on mainnet are vanishingly small so it is not - /// accounted for. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - pub async fn fill_suboptimal_attestations(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - let highest_filled_slot_opt = if self.config.attestations { - database::get_highest_attestation(&mut conn)? - .map(|attestation| attestation.epoch_start_slot.as_slot()) - } else { - return Err(Error::NotEnabled("attestations".to_string())); - }; - - let start_epoch = if let Some(highest_filled_slot) = highest_filled_slot_opt { - if highest_filled_slot % self.slots_per_epoch == self.slots_per_epoch.saturating_sub(1) - { - // The whole epoch is filled so we can begin syncing the next one. - highest_filled_slot.epoch(self.slots_per_epoch) + 1 - } else { - // The epoch is only partially synced. Try to sync it fully. - highest_filled_slot.epoch(self.slots_per_epoch) - } - } else { - // No rows present in the `suboptimal_attestations` table. Use `canonical_slots` - // instead. - if let Some(lowest_canonical_slot) = database::get_lowest_canonical_slot(&mut conn)? { - lowest_canonical_slot - .slot - .as_slot() - .epoch(self.slots_per_epoch) - } else { - // There are no slots in the database, do not fill the `suboptimal_attestations` - // table. - warn!("Refusing to fill the `suboptimal_attestations` table as there are no slots in the database"); - return Ok(()); - } - }; - - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut end_epoch = highest_canonical_slot.epoch(self.slots_per_epoch); - - // The `lighthouse/analysis/attestation_performance` endpoint can only retrieve attestations - // which are more than 1 epoch old. - // We assume that `highest_canonical_slot` is near the head of the chain. - end_epoch = end_epoch.saturating_sub(2_u64); - - // If end_epoch == 0 then the chain just started so we need to wait until - // `current_epoch >= 2`. - if end_epoch == 0 { - debug!("Chain just begun, refusing to sync attestations"); - return Ok(()); - } - - if start_epoch > end_epoch { - debug!("Attestations are up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) { - end_epoch = start_epoch + MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS - } - - if let Some(lowest_canonical_slot) = - database::get_lowest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut attestations = get_attestation_performances( - &self.bn, - start_epoch, - end_epoch, - self.slots_per_epoch, - ) - .await?; - - // Only insert attestations with corresponding `canonical_slot`s. - attestations.retain(|attestation| { - attestation.epoch_start_slot.as_slot() >= lowest_canonical_slot - && attestation.epoch_start_slot.as_slot() <= highest_canonical_slot - }); - database::insert_batch_suboptimal_attestations(&mut conn, attestations)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest canonical slot when one exists".to_string(), - ))); - } - } else { - // There are no slots in the `canonical_slots` table, but there are entries in the - // `suboptimal_attestations` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `suboptimal_attestations` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `attestation_performance` API endpoint with: - /// `start_epoch` -> epoch of the lowest `canonical_slot`. - /// `end_epoch` -> epoch of the lowest filled `suboptimal_attestation` - 1 (or epoch of highest - /// canonical slot) - /// - /// It will resync the lowest epoch if it is not fully filled. - /// That is, `if lowest_filled_slot % slots_per_epoch != 0` - /// - /// In the event there are no suboptimal attestations present in the lowest epoch, it will attempt to - /// resync the epoch. The odds of this occuring on mainnet are vanishingly small so it is not - /// accounted for. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - pub async fn backfill_suboptimal_attestations(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_attestation_backfill = self.config.max_backfill_size_epochs; - - // Get the slot of the lowest entry in the `suboptimal_attestations` table. - let lowest_filled_slot_opt = if self.config.attestations { - database::get_lowest_attestation(&mut conn)? - .map(|attestation| attestation.epoch_start_slot.as_slot()) - } else { - return Err(Error::NotEnabled("attestations".to_string())); - }; - - let end_epoch = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - if lowest_filled_slot % self.slots_per_epoch == 0 { - lowest_filled_slot - .epoch(self.slots_per_epoch) - .saturating_sub(1_u64) - } else { - // The epoch is only partially synced. Try to sync it fully. - lowest_filled_slot.epoch(self.slots_per_epoch) - } - } else { - // No entries in the `suboptimal_attestations` table. Use `canonical_slots` instead. - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - // Subtract 2 since `end_epoch` must be less than the current epoch - 1. - // We assume that `highest_canonical_slot` is near the head of the chain. - highest_canonical_slot - .epoch(self.slots_per_epoch) - .saturating_sub(2_u64) - } else { - // There are no slots in the database, do not backfill the - // `suboptimal_attestations` table. - warn!("Refusing to backfill attestations as there are no slots in the database"); - return Ok(()); - } - }; - - if end_epoch == 0 { - debug!("Attestations backfill is complete"); - return Ok(()); - } - - if let Some(lowest_canonical_slot) = - database::get_lowest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut start_epoch = lowest_canonical_slot.epoch(self.slots_per_epoch); - - if start_epoch > end_epoch { - debug!("Attestations are up to date with the base of the database"); - return Ok(()); - } - - // Ensure the request range does not exceed `max_attestation_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - if start_epoch < end_epoch.saturating_sub(max_attestation_backfill) { - start_epoch = end_epoch.saturating_sub(max_attestation_backfill) - } - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) { - start_epoch = end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) - } - - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut attestations = get_attestation_performances( - &self.bn, - start_epoch, - end_epoch, - self.slots_per_epoch, - ) - .await?; - - // Only insert `suboptimal_attestations` with corresponding `canonical_slots`. - attestations.retain(|attestation| { - attestation.epoch_start_slot.as_slot() >= lowest_canonical_slot - && attestation.epoch_start_slot.as_slot() <= highest_canonical_slot - }); - - database::insert_batch_suboptimal_attestations(&mut conn, attestations)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest slot when one exists".to_string(), - ))); - } - } else { - // There are no slots in the `canonical_slot` table, but there are entries in the - // `suboptimal_attestations` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/updater/config.rs b/watch/src/updater/config.rs deleted file mode 100644 index 0179be73db..0000000000 --- a/watch/src/updater/config.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const BEACON_NODE_URL: &str = "http://127.0.0.1:5052"; - -pub const fn max_backfill_size_epochs() -> u64 { - 2 -} -pub const fn backfill_stop_epoch() -> u64 { - 0 -} -pub const fn attestations() -> bool { - true -} -pub const fn proposer_info() -> bool { - true -} -pub const fn block_rewards() -> bool { - true -} -pub const fn block_packing() -> bool { - true -} - -fn beacon_node_url() -> String { - BEACON_NODE_URL.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - /// The URL of the beacon you wish to sync from. - #[serde(default = "beacon_node_url")] - pub beacon_node_url: String, - /// The maximum size each backfill iteration will allow per request (in epochs). - #[serde(default = "max_backfill_size_epochs")] - pub max_backfill_size_epochs: u64, - /// The epoch at which to never backfill past. - #[serde(default = "backfill_stop_epoch")] - pub backfill_stop_epoch: u64, - /// Whether to sync the suboptimal_attestations table. - #[serde(default = "attestations")] - pub attestations: bool, - /// Whether to sync the proposer_info table. - #[serde(default = "proposer_info")] - pub proposer_info: bool, - /// Whether to sync the block_rewards table. - #[serde(default = "block_rewards")] - pub block_rewards: bool, - /// Whether to sync the block_packing table. - #[serde(default = "block_packing")] - pub block_packing: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - beacon_node_url: beacon_node_url(), - max_backfill_size_epochs: max_backfill_size_epochs(), - backfill_stop_epoch: backfill_stop_epoch(), - attestations: attestations(), - proposer_info: proposer_info(), - block_rewards: block_rewards(), - block_packing: block_packing(), - } - } -} diff --git a/watch/src/updater/error.rs b/watch/src/updater/error.rs deleted file mode 100644 index 13c83bcf01..0000000000 --- a/watch/src/updater/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::blockprint::Error as BlockprintError; -use crate::database::Error as DbError; -use beacon_node::beacon_chain::BeaconChainError; -use eth2::{Error as Eth2Error, SensitiveError}; -use std::fmt; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - BeaconChain(BeaconChainError), - Eth2(Eth2Error), - SensitiveUrl(SensitiveError), - Database(DbError), - Blockprint(BlockprintError), - UnableToGetRemoteHead, - BeaconNodeSyncing, - NotEnabled(String), - NoValidatorsFound, - BeaconNodeNotCompatible(String), - InvalidConfig(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for Error { - fn from(e: BeaconChainError) -> Self { - Error::BeaconChain(e) - } -} - -impl From for Error { - fn from(e: Eth2Error) -> Self { - Error::Eth2(e) - } -} - -impl From for Error { - fn from(e: SensitiveError) -> Self { - Error::SensitiveUrl(e) - } -} - -impl From for Error { - fn from(e: DbError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: BlockprintError) -> Self { - Error::Blockprint(e) - } -} diff --git a/watch/src/updater/handler.rs b/watch/src/updater/handler.rs deleted file mode 100644 index 8f5e3f8e4a..0000000000 --- a/watch/src/updater/handler.rs +++ /dev/null @@ -1,471 +0,0 @@ -use crate::blockprint::WatchBlockprintClient; -use crate::config::Config as FullConfig; -use crate::database::{self, PgPool, WatchCanonicalSlot, WatchHash, WatchSlot}; -use crate::updater::{Config, Error, WatchSpec}; -use beacon_node::beacon_chain::BeaconChainError; -use eth2::{ - types::{BlockId, SyncingData}, - BeaconNodeHttpClient, SensitiveUrl, -}; -use log::{debug, error, info, warn}; -use std::collections::HashSet; -use std::marker::PhantomData; -use types::{BeaconBlockHeader, EthSpec, Hash256, SignedBeaconBlock, Slot}; - -use crate::updater::{get_beacon_block, get_header, get_validators}; - -const MAX_EXPECTED_REORG_LENGTH: u64 = 32; - -/// Ensure the existing database is valid for this run. -pub async fn ensure_valid_database( - spec: &WatchSpec, - pool: &mut PgPool, -) -> Result<(), Error> { - let mut conn = database::get_connection(pool)?; - - let bn_slots_per_epoch = spec.slots_per_epoch(); - let bn_config_name = spec.network.clone(); - - if let Some((db_config_name, db_slots_per_epoch)) = database::get_active_config(&mut conn)? { - if db_config_name != bn_config_name || db_slots_per_epoch != bn_slots_per_epoch as i32 { - Err(Error::InvalidConfig( - "The config stored in the database does not match the beacon node.".to_string(), - )) - } else { - // Configs match. - Ok(()) - } - } else { - // No config exists in the DB. - database::insert_active_config(&mut conn, bn_config_name, bn_slots_per_epoch)?; - Ok(()) - } -} - -pub struct UpdateHandler { - pub pool: PgPool, - pub bn: BeaconNodeHttpClient, - pub blockprint: Option, - pub config: Config, - pub slots_per_epoch: u64, - pub _phantom: PhantomData, -} - -impl UpdateHandler { - pub async fn new( - bn: BeaconNodeHttpClient, - spec: WatchSpec, - config: FullConfig, - ) -> Result, Error> { - let blockprint = if config.blockprint.enabled { - if let Some(server) = config.blockprint.url { - let blockprint_url = SensitiveUrl::parse(&server).map_err(Error::SensitiveUrl)?; - Some(WatchBlockprintClient { - client: reqwest::Client::new(), - server: blockprint_url, - username: config.blockprint.username, - password: config.blockprint.password, - }) - } else { - return Err(Error::NotEnabled( - "blockprint was enabled but url was not set".to_string(), - )); - } - } else { - None - }; - - let mut pool = database::build_connection_pool(&config.database)?; - - ensure_valid_database(&spec, &mut pool).await?; - - Ok(Self { - pool, - bn, - blockprint, - config: config.updater, - slots_per_epoch: spec.slots_per_epoch(), - _phantom: PhantomData, - }) - } - - /// Gets the syncing status of the connected beacon node. - pub async fn get_bn_syncing_status(&mut self) -> Result { - Ok(self.bn.get_node_syncing().await?.data) - } - - /// Gets a list of block roots from the database which do not yet contain a corresponding - /// entry in the `beacon_blocks` table and inserts them. - pub async fn update_unknown_blocks(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let roots = database::get_unknown_canonical_blocks(&mut conn)?; - for root in roots { - let block_opt: Option> = - get_beacon_block(&self.bn, BlockId::Root(root.as_hash())).await?; - if let Some(block) = block_opt { - database::insert_beacon_block(&mut conn, block, root)?; - } - } - - Ok(()) - } - - /// Performs a head update with the following steps: - /// 1. Pull the latest header from the beacon node and the latest canonical slot from the - /// database. - /// 2. Loop back through the beacon node and database to find the first matching slot -> root - /// pair. - /// 3. Go back `MAX_EXPECTED_REORG_LENGTH` slots through the database ensuring it is - /// consistent with the beacon node. If a re-org occurs beyond this range, we cannot recover. - /// 4. Remove any invalid slots from the database. - /// 5. Sync all blocks between the first valid block of the database and the head of the beacon - /// chain. - /// - /// In the event there are no slots present in the database, it will sync from the head block - /// block back to the first slot of the epoch. - /// This will ensure backfills are always done in full epochs (which helps keep certain syncing - /// tasks efficient). - pub async fn perform_head_update(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - // Load the head from the beacon node. - let bn_header = get_header(&self.bn, BlockId::Head) - .await? - .ok_or(Error::UnableToGetRemoteHead)?; - let header_root = bn_header.canonical_root(); - - if let Some(latest_matching_canonical_slot) = - self.get_first_matching_block(bn_header.clone()).await? - { - // Check for reorgs. - let latest_db_slot = self.check_for_reorg(latest_matching_canonical_slot).await?; - - // Remove all slots above `latest_db_slot` from the database. - let result = database::delete_canonical_slots_above( - &mut conn, - WatchSlot::from_slot(latest_db_slot), - )?; - info!("{result} old records removed during head update"); - - if result > 0 { - // If slots were removed, we need to resync the suboptimal_attestations table for - // the epoch since they will have changed and cannot be fixed by a simple update. - let epoch = latest_db_slot - .epoch(self.slots_per_epoch) - .saturating_sub(1_u64); - debug!("Preparing to resync attestations above epoch {epoch}"); - database::delete_suboptimal_attestations_above( - &mut conn, - WatchSlot::from_slot(epoch.start_slot(self.slots_per_epoch)), - )?; - } - - // Since we are syncing backwards, `start_slot > `end_slot`. - let start_slot = bn_header.slot; - let end_slot = latest_db_slot + 1; - self.reverse_fill_canonical_slots(bn_header, header_root, false, start_slot, end_slot) - .await?; - info!("Reverse sync begun at slot {start_slot} and stopped at slot {end_slot}"); - - // Attempt to sync new blocks with blockprint. - //self.sync_blockprint_until(start_slot).await?; - } else { - // There are no matching parent blocks. Sync from the head block back until the first - // block of the epoch. - let start_slot = bn_header.slot; - let end_slot = start_slot.saturating_sub(start_slot % self.slots_per_epoch); - self.reverse_fill_canonical_slots(bn_header, header_root, false, start_slot, end_slot) - .await?; - info!("Reverse sync begun at slot {start_slot} and stopped at slot {end_slot}"); - } - - Ok(()) - } - - /// Attempt to find a row in the `canonical_slots` table which matches the `canonical_root` of - /// the block header as reported by the beacon node. - /// - /// Any blocks above this value are not canonical according to the beacon node. - /// - /// Note: In the event that there are skip slots above the slot returned by the function, - /// they will not be returned, so may be pruned or re-synced by other code despite being - /// canonical. - pub async fn get_first_matching_block( - &mut self, - mut bn_header: BeaconBlockHeader, - ) -> Result, Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Load latest non-skipped canonical slot from database. - if let Some(db_canonical_slot) = - database::get_highest_non_skipped_canonical_slot(&mut conn)? - { - // Check if the header or parent root matches the entry in the database. - if bn_header.parent_root == db_canonical_slot.root.as_hash() - || bn_header.canonical_root() == db_canonical_slot.root.as_hash() - { - Ok(Some(db_canonical_slot)) - } else { - // Header is not the child of the highest entry in the database. - // From here we need to iterate backwards through the database until we find - // a slot -> root pair that matches the beacon node. - loop { - // Store working `parent_root`. - let parent_root = bn_header.parent_root; - - // Try the next header. - let next_header = get_header(&self.bn, BlockId::Root(parent_root)).await?; - if let Some(header) = next_header { - bn_header = header.clone(); - if let Some(db_canonical_slot) = database::get_canonical_slot_by_root( - &mut conn, - WatchHash::from_hash(header.parent_root), - )? { - // Check if the entry in the database matches the parent of - // the header. - if header.parent_root == db_canonical_slot.root.as_hash() { - return Ok(Some(db_canonical_slot)); - } else { - // Move on to the next header. - continue; - } - } else { - // Database does not have the referenced root. Try the next header. - continue; - } - } else { - // If we get this error it means that the `parent_root` of the header - // did not reference a canonical block. - return Err(Error::BeaconChain(BeaconChainError::MissingBeaconBlock( - parent_root, - ))); - } - } - } - } else { - // There are no non-skipped blocks present in the database. - Ok(None) - } - } - - /// Given the latest slot in the database which matches a root in the beacon node, - /// traverse back through the database for `MAX_EXPECTED_REORG_LENGTH` slots to ensure the tip - /// of the database is consistent with the beacon node (in the case that reorgs have occured). - /// - /// Returns the slot before the oldest canonical_slot which has an invalid child. - pub async fn check_for_reorg( - &mut self, - latest_canonical_slot: WatchCanonicalSlot, - ) -> Result { - let mut conn = database::get_connection(&self.pool)?; - - let end_slot = latest_canonical_slot.slot.as_u64(); - let start_slot = end_slot.saturating_sub(MAX_EXPECTED_REORG_LENGTH); - - for i in start_slot..end_slot { - let slot = Slot::new(i); - let db_canonical_slot_opt = - database::get_canonical_slot(&mut conn, WatchSlot::from_slot(slot))?; - if let Some(db_canonical_slot) = db_canonical_slot_opt { - let header_opt = get_header(&self.bn, BlockId::Slot(slot)).await?; - if let Some(header) = header_opt { - if header.canonical_root() == db_canonical_slot.root.as_hash() { - // The roots match (or are both skip slots). - continue; - } else { - // The block roots do not match. We need to re-sync from here. - warn!("Block {slot} does not match the beacon node. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } else if !db_canonical_slot.skipped { - // The block exists in the database, but does not exist on the beacon node. - // We need to re-sync from here. - warn!("Block {slot} does not exist on the beacon node. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } else { - // This slot does not exist in the database. - let lowest_slot = database::get_lowest_canonical_slot(&mut conn)? - .map(|canonical_slot| canonical_slot.slot.as_slot()); - if lowest_slot > Some(slot) { - // The database has not back-filled this slot yet, so skip it. - continue; - } else { - // The database does not contain this block, but has back-filled past it. - // We need to resync from here. - warn!("Slot {slot} missing from database. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } - } - - // The database is consistent with the beacon node, so return the head of the database. - Ok(latest_canonical_slot.slot.as_slot()) - } - - /// Fills the canonical slots table beginning from `start_slot` and ending at `end_slot`. - /// It fills in reverse order, that is, `start_slot` is higher than `end_slot`. - /// - /// Skip slots set `root` to the root of the previous non-skipped slot and also sets - /// `skipped == true`. - /// - /// Since it uses `insert_canonical_slot` to interact with the database, it WILL NOT overwrite - /// existing rows. This means that any part of the chain within `end_slot..=start_slot` that - /// needs to be resynced, must first be deleted from the database. - pub async fn reverse_fill_canonical_slots( - &mut self, - mut header: BeaconBlockHeader, - mut header_root: Hash256, - mut skipped: bool, - start_slot: Slot, - end_slot: Slot, - ) -> Result { - let mut count = 0; - - let mut conn = database::get_connection(&self.pool)?; - - // Iterate, descending from `start_slot` (higher) to `end_slot` (lower). - for slot in (end_slot.as_u64()..=start_slot.as_u64()).rev() { - // Insert header. - database::insert_canonical_slot( - &mut conn, - WatchCanonicalSlot { - slot: WatchSlot::new(slot), - root: WatchHash::from_hash(header_root), - skipped, - beacon_block: None, - }, - )?; - count += 1; - - // Load the next header: - // We must use BlockId::Slot since we want to include skip slots. - header = if let Some(new_header) = get_header( - &self.bn, - BlockId::Slot(Slot::new(slot.saturating_sub(1_u64))), - ) - .await? - { - header_root = new_header.canonical_root(); - skipped = false; - new_header - } else { - if header.slot == 0 { - info!("Reverse fill exhausted at slot 0"); - break; - } - // Slot was skipped, so use the parent_root (most recent non-skipped block). - skipped = true; - header_root = header.parent_root; - header - }; - } - - Ok(count) - } - - /// Backfills the `canonical_slots` table starting from the lowest non-skipped slot and - /// stopping after `max_backfill_size_epochs` epochs. - pub async fn backfill_canonical_slots(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let backfill_stop_slot = self.config.backfill_stop_epoch * self.slots_per_epoch; - // Check to see if we have finished backfilling. - if let Some(lowest_slot) = database::get_lowest_canonical_slot(&mut conn)? { - if lowest_slot.slot.as_slot() == backfill_stop_slot { - debug!("Backfill sync complete, all slots filled"); - return Ok(()); - } - } - - let backfill_slot_count = self.config.max_backfill_size_epochs * self.slots_per_epoch; - - if let Some(lowest_non_skipped_canonical_slot) = - database::get_lowest_non_skipped_canonical_slot(&mut conn)? - { - // Set `start_slot` equal to the lowest non-skipped slot in the database. - // While this will attempt to resync some parts of the bottom of the chain, it reduces - // complexity when dealing with skip slots. - let start_slot = lowest_non_skipped_canonical_slot.slot.as_slot(); - let mut end_slot = lowest_non_skipped_canonical_slot - .slot - .as_slot() - .saturating_sub(backfill_slot_count); - - // Ensure end_slot doesn't go below `backfill_stop_epoch` - if end_slot <= backfill_stop_slot { - end_slot = Slot::new(backfill_stop_slot); - } - - let header_opt = get_header(&self.bn, BlockId::Slot(start_slot)).await?; - - if let Some(header) = header_opt { - let header_root = header.canonical_root(); - let count = self - .reverse_fill_canonical_slots(header, header_root, false, start_slot, end_slot) - .await?; - - info!("Backfill completed to slot: {end_slot}, records added: {count}"); - } else { - // The lowest slot of the database is inconsistent with the beacon node. - // Currently we have no way to recover from this. The entire database will need to - // be re-synced. - error!( - "Database is inconsistent with the beacon node. \ - Please ensure your beacon node is set to the right network, \ - otherwise you may need to resync" - ); - } - } else { - // There are no blocks in the database. Forward sync needs to happen first. - info!("Backfill was not performed since there are no blocks in the database"); - return Ok(()); - }; - - Ok(()) - } - - // Attempt to update the validator set. - // This downloads the latest validator set from the beacon node, and pulls the known validator - // set from the database. - // We then take any new or updated validators and insert them into the database (overwriting - // exiting validators). - // - // In the event there are no validators in the database, it will initialize the validator set. - pub async fn update_validator_set(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - let current_validators = database::get_all_validators(&mut conn)?; - - if !current_validators.is_empty() { - let old_validators = HashSet::from_iter(current_validators); - - // Pull the new validator set from the beacon node. - let new_validators = get_validators(&self.bn).await?; - - // The difference should only contain validators that contain either a new `exit_epoch` (implying an - // exit) or a new `index` (implying a validator activation). - let val_diff = new_validators.difference(&old_validators); - - for diff in val_diff { - database::insert_validator(&mut conn, diff.clone())?; - } - } else { - info!("No validators present in database. Initializing the validator set"); - self.initialize_validator_set().await?; - } - - Ok(()) - } - - // Initialize the validator set by downloading it from the beacon node, inserting blockprint - // data (if required) and writing it to the database. - pub async fn initialize_validator_set(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Pull all validators from the beacon node. - let validators = Vec::from_iter(get_validators(&self.bn).await?); - - database::insert_batch_validators(&mut conn, validators)?; - - Ok(()) - } -} diff --git a/watch/src/updater/mod.rs b/watch/src/updater/mod.rs deleted file mode 100644 index 65e0a90a2b..0000000000 --- a/watch/src/updater/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::config::Config as FullConfig; -use crate::database::{WatchPK, WatchValidator}; -use eth2::{ - types::{BlockId, StateId}, - BeaconNodeHttpClient, SensitiveUrl, Timeouts, -}; -use log::{debug, error, info}; -use std::collections::{HashMap, HashSet}; -use std::marker::PhantomData; -use std::time::{Duration, Instant}; -use types::{BeaconBlockHeader, EthSpec, GnosisEthSpec, MainnetEthSpec, SignedBeaconBlock}; - -pub use config::Config; -pub use error::Error; -pub use handler::UpdateHandler; - -mod config; -pub mod error; -pub mod handler; - -const FAR_FUTURE_EPOCH: u64 = u64::MAX; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); - -const MAINNET: &str = "mainnet"; -const GNOSIS: &str = "gnosis"; - -pub struct WatchSpec { - network: String, - spec: PhantomData, -} - -impl WatchSpec { - fn slots_per_epoch(&self) -> u64 { - E::slots_per_epoch() - } -} - -impl WatchSpec { - pub fn mainnet(network: String) -> Self { - Self { - network, - spec: PhantomData, - } - } -} - -impl WatchSpec { - fn gnosis(network: String) -> Self { - Self { - network, - spec: PhantomData, - } - } -} - -pub async fn run_updater(config: FullConfig) -> Result<(), Error> { - let beacon_node_url = - SensitiveUrl::parse(&config.updater.beacon_node_url).map_err(Error::SensitiveUrl)?; - let bn = BeaconNodeHttpClient::new(beacon_node_url, Timeouts::set_all(DEFAULT_TIMEOUT)); - - let config_map = bn.get_config_spec::>().await?.data; - - let config_name = config_map - .get("CONFIG_NAME") - .ok_or_else(|| { - Error::BeaconNodeNotCompatible("No field CONFIG_NAME on beacon node spec".to_string()) - })? - .clone(); - - match config_map - .get("PRESET_BASE") - .ok_or_else(|| { - Error::BeaconNodeNotCompatible("No field PRESET_BASE on beacon node spec".to_string()) - })? - .to_lowercase() - .as_str() - { - MAINNET => { - let spec = WatchSpec::mainnet(config_name); - run_once(bn, spec, config).await - } - GNOSIS => { - let spec = WatchSpec::gnosis(config_name); - run_once(bn, spec, config).await - } - _ => unimplemented!("unsupported PRESET_BASE"), - } -} - -pub async fn run_once( - bn: BeaconNodeHttpClient, - spec: WatchSpec, - config: FullConfig, -) -> Result<(), Error> { - let mut watch = UpdateHandler::new(bn, spec, config.clone()).await?; - - let sync_data = watch.get_bn_syncing_status().await?; - if sync_data.is_syncing { - error!( - "Connected beacon node is still syncing: head_slot => {:?}, distance => {}", - sync_data.head_slot, sync_data.sync_distance - ); - return Err(Error::BeaconNodeSyncing); - } - - info!("Performing head update"); - let head_timer = Instant::now(); - watch.perform_head_update().await?; - let head_timer_elapsed = head_timer.elapsed(); - debug!("Head update complete, time taken: {head_timer_elapsed:?}"); - - info!("Performing block backfill"); - let block_backfill_timer = Instant::now(); - watch.backfill_canonical_slots().await?; - let block_backfill_timer_elapsed = block_backfill_timer.elapsed(); - debug!("Block backfill complete, time taken: {block_backfill_timer_elapsed:?}"); - - info!("Updating validator set"); - let validator_timer = Instant::now(); - watch.update_validator_set().await?; - let validator_timer_elapsed = validator_timer.elapsed(); - debug!("Validator update complete, time taken: {validator_timer_elapsed:?}"); - - // Update blocks after updating the validator set since the `proposer_index` must exist in the - // `validators` table. - info!("Updating unknown blocks"); - let unknown_block_timer = Instant::now(); - watch.update_unknown_blocks().await?; - let unknown_block_timer_elapsed = unknown_block_timer.elapsed(); - debug!("Unknown block update complete, time taken: {unknown_block_timer_elapsed:?}"); - - // Run additional modules - if config.updater.attestations { - info!("Updating suboptimal attestations"); - let attestation_timer = Instant::now(); - watch.fill_suboptimal_attestations().await?; - watch.backfill_suboptimal_attestations().await?; - let attestation_timer_elapsed = attestation_timer.elapsed(); - debug!("Attestation update complete, time taken: {attestation_timer_elapsed:?}"); - } - - if config.updater.block_rewards { - info!("Updating block rewards"); - let rewards_timer = Instant::now(); - watch.fill_block_rewards().await?; - watch.backfill_block_rewards().await?; - let rewards_timer_elapsed = rewards_timer.elapsed(); - debug!("Block Rewards update complete, time taken: {rewards_timer_elapsed:?}"); - } - - if config.updater.block_packing { - info!("Updating block packing statistics"); - let packing_timer = Instant::now(); - watch.fill_block_packing().await?; - watch.backfill_block_packing().await?; - let packing_timer_elapsed = packing_timer.elapsed(); - debug!("Block packing update complete, time taken: {packing_timer_elapsed:?}"); - } - - if config.blockprint.enabled { - info!("Updating blockprint"); - let blockprint_timer = Instant::now(); - watch.fill_blockprint().await?; - watch.backfill_blockprint().await?; - let blockprint_timer_elapsed = blockprint_timer.elapsed(); - debug!("Blockprint update complete, time taken: {blockprint_timer_elapsed:?}"); - } - - Ok(()) -} - -/// Queries the beacon node for a given `BlockId` and returns the `BeaconBlockHeader` if it exists. -pub async fn get_header( - bn: &BeaconNodeHttpClient, - block_id: BlockId, -) -> Result, Error> { - let resp = bn - .get_beacon_headers_block_id(block_id) - .await? - .map(|resp| (resp.data.root, resp.data.header.message)); - // When quering with root == 0x000... , slot 0 will be returned with parent_root == 0x0000... - // This check escapes the loop. - if let Some((root, header)) = resp { - if root == header.parent_root { - return Ok(None); - } else { - return Ok(Some(header)); - } - } - Ok(None) -} - -pub async fn get_beacon_block( - bn: &BeaconNodeHttpClient, - block_id: BlockId, -) -> Result>, Error> { - let block = bn.get_beacon_blocks(block_id).await?.map(|resp| resp.data); - - Ok(block) -} - -/// Queries the beacon node for the current validator set. -pub async fn get_validators(bn: &BeaconNodeHttpClient) -> Result, Error> { - let mut validator_map = HashSet::new(); - - let validators = bn - .get_beacon_states_validators(StateId::Head, None, None) - .await? - .ok_or(Error::NoValidatorsFound)? - .data; - - for val in validators { - // Only store `activation_epoch` if it not the `FAR_FUTURE_EPOCH`. - let activation_epoch = if val.validator.activation_epoch.as_u64() == FAR_FUTURE_EPOCH { - None - } else { - Some(val.validator.activation_epoch.as_u64() as i32) - }; - // Only store `exit_epoch` if it is not the `FAR_FUTURE_EPOCH`. - let exit_epoch = if val.validator.exit_epoch.as_u64() == FAR_FUTURE_EPOCH { - None - } else { - Some(val.validator.exit_epoch.as_u64() as i32) - }; - validator_map.insert(WatchValidator { - index: val.index as i32, - public_key: WatchPK::from_pubkey(val.validator.pubkey), - status: val.status.to_string(), - activation_epoch, - exit_epoch, - }); - } - Ok(validator_map) -} diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs deleted file mode 100644 index e21cf151b1..0000000000 --- a/watch/tests/tests.rs +++ /dev/null @@ -1,1294 +0,0 @@ -#![recursion_limit = "256"] -#![cfg(unix)] - -use beacon_chain::{ - test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType}, - ChainConfig, -}; -use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; -use http_api::test_utils::{create_api_server, ApiServer}; -use log::error; -use logging::test_logger; -use network::NetworkReceivers; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; -use std::collections::HashMap; -use std::env; -use std::time::Duration; -use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage}; -use tokio::{runtime, task::JoinHandle}; -use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls}; -use types::{Hash256, MainnetEthSpec, Slot}; -use unused_port::unused_tcp4_port; -use url::Url; -use watch::{ - client::WatchHttpClient, - config::Config, - database::{self, Config as DatabaseConfig, PgPool, WatchSlot}, - server::{start_server, Config as ServerConfig}, - updater::{handler::*, run_updater, Config as UpdaterConfig, WatchSpec}, -}; - -#[derive(Debug)] -pub struct Postgres(HashMap); - -impl Default for Postgres { - fn default() -> Self { - let mut env_vars = HashMap::new(); - env_vars.insert("POSTGRES_DB".to_owned(), "postgres".to_owned()); - env_vars.insert("POSTGRES_HOST_AUTH_METHOD".into(), "trust".into()); - - Self(env_vars) - } -} - -impl Image for Postgres { - type Args = (); - - fn name(&self) -> String { - "postgres".to_owned() - } - - fn tag(&self) -> String { - "11-alpine".to_owned() - } - - fn ready_conditions(&self) -> Vec { - vec![WaitFor::message_on_stderr( - "database system is ready to accept connections", - )] - } - - fn env_vars(&self) -> Box + '_> { - Box::new(self.0.iter()) - } -} - -type E = MainnetEthSpec; - -const VALIDATOR_COUNT: usize = 32; -const SLOTS_PER_EPOCH: u64 = 32; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); - -/// Set this environment variable to use a different hostname for connecting to -/// the database. Can be set to `host.docker.internal` for docker-in-docker -/// setups. -const WATCH_HOST_ENV_VARIABLE: &str = "WATCH_HOST"; - -fn build_test_config(config: &DatabaseConfig) -> PostgresConfig { - let mut postgres_config = PostgresConfig::new(); - postgres_config - .user(&config.user) - .password(&config.password) - .dbname(&config.default_dbname) - .host(&config.host) - .port(config.port) - .connect_timeout(Duration::from_millis(config.connect_timeout_millis)); - postgres_config -} - -async fn connect(config: &DatabaseConfig) -> (Client, JoinHandle<()>) { - let db_config = build_test_config(config); - let (client, conn) = db_config - .connect(NoTls) - .await - .expect("Could not connect to db"); - let connection = runtime::Handle::current().spawn(async move { - if let Err(e) = conn.await { - error!("Connection error {:?}", e); - } - }); - - (client, connection) -} - -pub async fn create_test_database(config: &DatabaseConfig) { - let (db, _) = connect(config).await; - - db.execute(&format!("CREATE DATABASE {};", config.dbname), &[]) - .await - .expect("Database creation failed"); -} - -pub fn get_host_from_env() -> String { - env::var(WATCH_HOST_ENV_VARIABLE).unwrap_or_else(|_| "localhost".to_string()) -} - -struct TesterBuilder { - pub harness: BeaconChainHarness>, - pub config: Config, - _bn_network_rx: NetworkReceivers, -} - -impl TesterBuilder { - pub async fn new() -> TesterBuilder { - let harness = BeaconChainHarness::builder(E::default()) - .default_spec() - .chain_config(ChainConfig { - reconstruct_historic_states: true, - ..ChainConfig::default() - }) - .logger(test_logger()) - .deterministic_keypairs(VALIDATOR_COUNT) - .fresh_ephemeral_store() - .build(); - - /* - * Spawn a Beacon Node HTTP API. - */ - let ApiServer { - server, - listening_socket: bn_api_listening_socket, - network_rx: _bn_network_rx, - .. - } = create_api_server( - harness.chain.clone(), - &harness.runtime, - harness.logger().clone(), - ) - .await; - tokio::spawn(server); - - /* - * Create a watch configuration - */ - let database_port = unused_tcp4_port().expect("Unable to find unused port."); - let server_port = 0; - let config = Config { - database: DatabaseConfig { - dbname: random_dbname(), - port: database_port, - host: get_host_from_env(), - ..Default::default() - }, - server: ServerConfig { - listen_port: server_port, - ..Default::default() - }, - updater: UpdaterConfig { - beacon_node_url: format!( - "http://{}:{}", - bn_api_listening_socket.ip(), - bn_api_listening_socket.port() - ), - ..Default::default() - }, - ..Default::default() - }; - - Self { - harness, - config, - _bn_network_rx, - } - } - pub async fn build(self, pool: PgPool) -> Tester { - /* - * Spawn a Watch HTTP API. - */ - let (addr, watch_server) = start_server(&self.config, SLOTS_PER_EPOCH, pool).unwrap(); - tokio::spawn(watch_server); - - /* - * Create a HTTP client to talk to the watch HTTP API. - */ - let client = WatchHttpClient { - client: reqwest::Client::new(), - server: Url::parse(&format!("http://{}:{}", addr.ip(), addr.port())).unwrap(), - }; - - /* - * Create a HTTP client to talk to the Beacon Node API. - */ - let beacon_node_url = SensitiveUrl::parse(&self.config.updater.beacon_node_url).unwrap(); - let bn = BeaconNodeHttpClient::new(beacon_node_url, Timeouts::set_all(DEFAULT_TIMEOUT)); - let spec = WatchSpec::mainnet("mainnet".to_string()); - - /* - * Build update service - */ - let updater = UpdateHandler::new(bn, spec, self.config.clone()) - .await - .unwrap(); - - Tester { - harness: self.harness, - client, - config: self.config, - updater, - _bn_network_rx: self._bn_network_rx, - } - } - async fn initialize_database(&self) -> PgPool { - create_test_database(&self.config.database).await; - database::utils::run_migrations(&self.config.database); - database::build_connection_pool(&self.config.database) - .expect("Could not build connection pool") - } -} - -struct Tester { - pub harness: BeaconChainHarness>, - pub client: WatchHttpClient, - pub config: Config, - pub updater: UpdateHandler, - _bn_network_rx: NetworkReceivers, -} - -impl Tester { - /// Extend the chain on the beacon chain harness. Do not update the beacon watch database. - pub async fn extend_chain(&mut self, num_blocks: u64) -> &mut Self { - self.harness.advance_slot(); - self.harness - .extend_chain( - num_blocks as usize, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ) - .await; - self - } - - // Advance the slot clock without a block. This results in a skipped slot. - pub fn skip_slot(&mut self) -> &mut Self { - self.harness.advance_slot(); - self - } - - // Perform a single slot re-org. - pub async fn reorg_chain(&mut self) -> &mut Self { - let previous_slot = self.harness.get_current_slot(); - self.harness.advance_slot(); - let first_slot = self.harness.get_current_slot(); - self.harness - .extend_chain( - 1, - BlockStrategy::ForkCanonicalChainAt { - previous_slot, - first_slot, - }, - AttestationStrategy::AllValidators, - ) - .await; - self - } - - /// Run the watch updater service. - pub async fn run_update_service(&mut self, num_runs: usize) -> &mut Self { - for _ in 0..num_runs { - run_updater(self.config.clone()).await.unwrap(); - } - self - } - - pub async fn perform_head_update(&mut self) -> &mut Self { - self.updater.perform_head_update().await.unwrap(); - self - } - - pub async fn perform_backfill(&mut self) -> &mut Self { - self.updater.backfill_canonical_slots().await.unwrap(); - self - } - - pub async fn update_unknown_blocks(&mut self) -> &mut Self { - self.updater.update_unknown_blocks().await.unwrap(); - self - } - - pub async fn update_validator_set(&mut self) -> &mut Self { - self.updater.update_validator_set().await.unwrap(); - self - } - - pub async fn fill_suboptimal_attestations(&mut self) -> &mut Self { - self.updater.fill_suboptimal_attestations().await.unwrap(); - - self - } - - pub async fn backfill_suboptimal_attestations(&mut self) -> &mut Self { - self.updater - .backfill_suboptimal_attestations() - .await - .unwrap(); - - self - } - - pub async fn fill_block_rewards(&mut self) -> &mut Self { - self.updater.fill_block_rewards().await.unwrap(); - - self - } - - pub async fn backfill_block_rewards(&mut self) -> &mut Self { - self.updater.backfill_block_rewards().await.unwrap(); - - self - } - - pub async fn fill_block_packing(&mut self) -> &mut Self { - self.updater.fill_block_packing().await.unwrap(); - - self - } - - pub async fn backfill_block_packing(&mut self) -> &mut Self { - self.updater.backfill_block_packing().await.unwrap(); - - self - } - - pub async fn assert_canonical_slots_empty(&mut self) -> &mut Self { - let lowest_slot = self - .client - .get_lowest_canonical_slot() - .await - .unwrap() - .map(|slot| slot.slot.as_slot()); - - assert_eq!(lowest_slot, None); - - self - } - - pub async fn assert_lowest_canonical_slot(&mut self, expected: u64) -> &mut Self { - let slot = self - .client - .get_lowest_canonical_slot() - .await - .unwrap() - .unwrap() - .slot - .as_slot(); - - assert_eq!(slot, Slot::new(expected)); - - self - } - - pub async fn assert_highest_canonical_slot(&mut self, expected: u64) -> &mut Self { - let slot = self - .client - .get_highest_canonical_slot() - .await - .unwrap() - .unwrap() - .slot - .as_slot(); - - assert_eq!(slot, Slot::new(expected)); - - self - } - - pub async fn assert_canonical_slots_not_empty(&mut self) -> &mut Self { - self.client - .get_lowest_canonical_slot() - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_slot_is_skipped(&mut self, slot: u64) -> &mut Self { - assert!(self - .client - .get_beacon_blocks(BlockId::Slot(Slot::new(slot))) - .await - .unwrap() - .is_none()); - self - } - - pub async fn assert_all_validators_exist(&mut self) -> &mut Self { - assert_eq!( - self.client - .get_all_validators() - .await - .unwrap() - .unwrap() - .len(), - VALIDATOR_COUNT - ); - self - } - - pub async fn assert_lowest_block_has_proposer_info(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - if block.slot.as_slot() == 0 { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_proposer_info(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_proposer_info(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_proposer_info(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_lowest_block_has_block_rewards(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - if block.slot.as_slot() == 0 { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_block_reward(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_block_rewards(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_block_reward(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_lowest_block_has_block_packing(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - while block.slot.as_slot() <= SLOTS_PER_EPOCH { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_block_packing(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_block_packing(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_block_packing(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - /// Check that the canonical chain in watch matches that of the harness. Also check that all - /// canonical blocks can be retrieved. - pub async fn assert_canonical_chain_consistent(&mut self, last_slot: u64) -> &mut Self { - let head_root = self.harness.chain.head_beacon_block_root(); - let mut chain: Vec<(Hash256, Slot)> = self - .harness - .chain - .rev_iter_block_roots_from(head_root) - .unwrap() - .map(Result::unwrap) - .collect(); - - // `chain` contains skip slots, but the `watch` API will not return blocks that do not - // exist. - // We need to filter them out. - chain.reverse(); - chain.dedup_by(|(hash1, _), (hash2, _)| hash1 == hash2); - - // Remove any slots below `last_slot` since it is known that the database has not - // backfilled past it. - chain.retain(|(_, slot)| slot.as_u64() >= last_slot); - - for (root, slot) in &chain { - let block = self - .client - .get_beacon_blocks(BlockId::Root(*root)) - .await - .unwrap() - .unwrap(); - assert_eq!(block.slot.as_slot(), *slot); - } - - self - } - - /// Check that every block in the `beacon_blocks` table has corresponding entries in the - /// `proposer_info`, `block_rewards` and `block_packing` tables. - pub async fn assert_all_blocks_have_metadata(&mut self) -> &mut Self { - let pool = database::build_connection_pool(&self.config.database).unwrap(); - - let mut conn = database::get_connection(&pool).unwrap(); - let highest_block_slot = database::get_highest_beacon_block(&mut conn) - .unwrap() - .unwrap() - .slot - .as_slot(); - let lowest_block_slot = database::get_lowest_beacon_block(&mut conn) - .unwrap() - .unwrap() - .slot - .as_slot(); - for slot in lowest_block_slot.as_u64()..=highest_block_slot.as_u64() { - let canonical_slot = database::get_canonical_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - if !canonical_slot.skipped { - database::get_block_rewards_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - database::get_proposer_info_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - database::get_block_packing_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - } - } - - self - } -} - -pub fn random_dbname() -> String { - let mut s: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(8) - .map(char::from) - .collect(); - // Postgres gets weird about capitals in database names. - s.make_ascii_lowercase(); - format!("test_{}", s) -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(16) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_sync_starts_on_skip_slot() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .skip_slot() - .skip_slot() - .extend_chain(6) - .await - .skip_slot() - .extend_chain(6) - .await - .skip_slot() - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_canonical_chain_consistent(0) - .await - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_with_skip_slot() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(5) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_highest_canonical_slot(5) - .await - .assert_lowest_canonical_slot(0) - .await - .assert_canonical_chain_consistent(0) - .await - .skip_slot() - .extend_chain(1) - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_highest_canonical_slot(7) - .await - .assert_slot_is_skipped(6) - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_with_reorg() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(5) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_highest_canonical_slot(5) - .await - .assert_lowest_canonical_slot(0) - .await - .assert_canonical_chain_consistent(0) - .await - .skip_slot() - .reorg_chain() - .await - .extend_chain(1) - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_highest_canonical_slot(8) - .await - .assert_slot_is_skipped(6) - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn chain_grows() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - // Apply four blocks to the chain. - tester - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(5) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(7) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -#[allow(clippy::large_stack_frames)] -async fn chain_grows_with_metadata() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - // Apply four blocks to the chain. - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_rewards() - .await - .fill_block_rewards() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(5) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(7) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await; -} - -#[cfg(unix)] -#[tokio::test] -#[allow(clippy::large_stack_frames)] -async fn chain_grows_with_metadata_and_multiple_skip_slots() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - // Apply four blocks to the chain. - tester - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - // And also backfill to the epoch boundary. - .await - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // Add multiple skip slots. - .skip_slot() - .skip_slot() - .skip_slot() - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(8) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(10) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_rewards() - .await - .fill_block_rewards() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn chain_grows_to_second_epoch() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - // Apply 40 blocks to the chain. - tester - .extend_chain(40) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(40) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(32) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(0) - .await - // Get block packings. - .fill_block_packing() - .await - .backfill_block_packing() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(40) - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Skip a slot - .skip_slot() - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(43) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Update new block_packing - // Backfill before forward fill to ensure order is arbitrary - .backfill_block_packing() - .await - .fill_block_packing() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn large_chain() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - // Apply 40 blocks to the chain. - tester - .extend_chain(400) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(400) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(384) - .await - // Backfill 2 epochs as per default config. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(384) - .await - // Get block rewards and proposer info. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // Get block packings. - .fill_block_packing() - .await - .backfill_block_packing() - .await - // Should have backfilled 2 more epochs. - .assert_lowest_canonical_slot(320) - .await - .assert_highest_canonical_slot(400) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Skip a slot - .skip_slot() - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - .perform_backfill() - .await - // Should have backfilled 2 more epochs - .assert_lowest_canonical_slot(256) - .await - .assert_highest_canonical_slot(403) - .await - // Update validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Get suboptimal attestations. - .fill_suboptimal_attestations() - .await - .backfill_suboptimal_attestations() - .await - // Get block rewards and proposer info. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // Get block packing. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_packing() - .await - .fill_block_packing() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(256) - .await - // Check every block has rewards, proposer info and packing statistics. - .assert_all_blocks_have_metadata() - .await; -} diff --git a/wordlist.txt b/wordlist.txt index bb8b46b525..7adbfe9032 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -12,6 +12,7 @@ BN BNs BTC BTEC +Btrfs Casper CentOS Chiado @@ -38,6 +39,7 @@ Exercism Extractable FFG Geth +GiB Gitcoin Gnosis Goerli @@ -91,6 +93,7 @@ TLS TODOs UDP UI +Uncached UPnP USD UX @@ -100,6 +103,7 @@ VCs VPN Withdrawable WSL +XFS YAML aarch anonymize @@ -196,6 +200,7 @@ redb reimport resync roadmap +routable rustfmt rustup schemas @@ -220,6 +225,7 @@ tweakers ui unadvanced unaggregated +uncached unencrypted unfinalized untrusted