mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
Merge branch 'main' into expose-support-flags
Some checks failed
Deny / audit (push) Has been cancelled
Some checks failed
Deny / audit (push) Has been cancelled
This commit is contained in:
commit
bd711aff7d
159 changed files with 4765 additions and 1264 deletions
376
Cargo.lock
generated
376
Cargo.lock
generated
|
@ -29,6 +29,15 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -44,6 +53,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anes"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
@ -68,6 +83,16 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert-json-diff"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -337,6 +362,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.31"
|
version = "1.1.31"
|
||||||
|
@ -370,6 +401,33 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"ciborium-ll",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-io"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-ll"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"half",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.20"
|
version = "4.5.20"
|
||||||
|
@ -388,6 +446,7 @@ checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -468,6 +527,42 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "criterion"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||||
|
dependencies = [
|
||||||
|
"anes",
|
||||||
|
"cast",
|
||||||
|
"ciborium",
|
||||||
|
"clap",
|
||||||
|
"criterion-plot",
|
||||||
|
"is-terminal",
|
||||||
|
"itertools",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"oorandom",
|
||||||
|
"plotters",
|
||||||
|
"rayon",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"tinytemplate",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "criterion-plot"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||||
|
dependencies = [
|
||||||
|
"cast",
|
||||||
|
"itertools",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam"
|
name = "crossbeam"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -524,6 +619,12 @@ version = "0.8.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-bigint"
|
name = "crypto-bigint"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
|
@ -573,12 +674,37 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-benchmark"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cuprate-benchmark-example",
|
||||||
|
"cuprate-benchmark-lib",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-benchmark-example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cuprate-benchmark-lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-benchmark-lib"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cuprate-blockchain"
|
name = "cuprate-blockchain"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"bytes",
|
||||||
"cuprate-constants",
|
"cuprate-constants",
|
||||||
"cuprate-database",
|
"cuprate-database",
|
||||||
"cuprate-database-service",
|
"cuprate-database-service",
|
||||||
|
@ -675,6 +801,25 @@ dependencies = [
|
||||||
name = "cuprate-constants"
|
name = "cuprate-constants"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-criterion-example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"criterion",
|
||||||
|
"function_name",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-criterion-json-rpc"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"criterion",
|
||||||
|
"cuprate-json-rpc",
|
||||||
|
"function_name",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cuprate-cryptonight"
|
name = "cuprate-cryptonight"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -790,6 +935,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
@ -837,7 +983,6 @@ dependencies = [
|
||||||
"cuprate-test-utils",
|
"cuprate-test-utils",
|
||||||
"cuprate-types",
|
"cuprate-types",
|
||||||
"cuprate-wire",
|
"cuprate-wire",
|
||||||
"dashmap",
|
|
||||||
"futures",
|
"futures",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
|
@ -1013,6 +1158,17 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cuprate-zmq-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"assert-json-diff",
|
||||||
|
"cuprate-types",
|
||||||
|
"hex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cuprated"
|
name = "cuprated"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -1035,7 +1191,6 @@ dependencies = [
|
||||||
"cuprate-consensus",
|
"cuprate-consensus",
|
||||||
"cuprate-consensus-context",
|
"cuprate-consensus-context",
|
||||||
"cuprate-consensus-rules",
|
"cuprate-consensus-rules",
|
||||||
"cuprate-constants",
|
|
||||||
"cuprate-cryptonight",
|
"cuprate-cryptonight",
|
||||||
"cuprate-dandelion-tower",
|
"cuprate-dandelion-tower",
|
||||||
"cuprate-database",
|
"cuprate-database",
|
||||||
|
@ -1077,6 +1232,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"toml",
|
||||||
"tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)",
|
"tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
@ -1284,6 +1440,21 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "function_name"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7"
|
||||||
|
dependencies = [
|
||||||
|
"function_name-proc-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "function_name-proc-macro"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -1433,6 +1604,16 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
@ -1660,6 +1841,26 @@ dependencies = [
|
||||||
"hashbrown 0.15.0",
|
"hashbrown 0.15.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -1756,6 +1957,15 @@ version = "0.4.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
@ -2011,6 +2221,12 @@ version = "1.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -2148,6 +2364,34 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"plotters-backend",
|
||||||
|
"plotters-svg",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-backend"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-svg"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705"
|
||||||
|
dependencies = [
|
||||||
|
"plotters-backend",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
@ -2222,7 +2466,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_xorshift",
|
"rand_xorshift",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.5",
|
||||||
"rusty-fork",
|
"rusty-fork",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"unarray",
|
"unarray",
|
||||||
|
@ -2388,6 +2632,44 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata 0.4.7",
|
||||||
|
"regex-syntax 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -2515,6 +2797,15 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
|
@ -2616,6 +2907,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -2833,6 +3133,16 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
|
@ -2869,6 +3179,16 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a693d0c8cf16973fac5a93fbe47b8c6452e7097d4fcac49f3d7a18e39c76e62e"
|
checksum = "a693d0c8cf16973fac5a93fbe47b8c6452e7097d4fcac49f3d7a18e39c76e62e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinytemplate"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -2964,11 +3284,26 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
|
@ -2977,6 +3312,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
@ -3085,10 +3422,14 @@ version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
@ -3194,6 +3535,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -3264,6 +3615,16 @@ version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.26.6"
|
version = "0.26.6"
|
||||||
|
@ -3289,6 +3650,15 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
100
Cargo.toml
100
Cargo.toml
|
@ -1,38 +1,61 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
|
# Binaries
|
||||||
"binaries/cuprated",
|
"binaries/cuprated",
|
||||||
"constants",
|
|
||||||
|
# Benchmarks
|
||||||
|
"benches/benchmark/bin",
|
||||||
|
"benches/benchmark/lib",
|
||||||
|
"benches/benchmark/example",
|
||||||
|
"benches/criterion/example",
|
||||||
|
"benches/criterion/cuprate-json-rpc",
|
||||||
|
|
||||||
|
# Consensus
|
||||||
"consensus",
|
"consensus",
|
||||||
"consensus/context",
|
"consensus/context",
|
||||||
"consensus/fast-sync",
|
"consensus/fast-sync",
|
||||||
"consensus/rules",
|
"consensus/rules",
|
||||||
"cryptonight",
|
|
||||||
"helper",
|
# Net
|
||||||
"net/epee-encoding",
|
"net/epee-encoding",
|
||||||
"net/fixed-bytes",
|
"net/fixed-bytes",
|
||||||
"net/levin",
|
"net/levin",
|
||||||
"net/wire",
|
"net/wire",
|
||||||
|
|
||||||
|
# P2P
|
||||||
"p2p/p2p",
|
"p2p/p2p",
|
||||||
"p2p/p2p-core",
|
"p2p/p2p-core",
|
||||||
"p2p/bucket",
|
"p2p/bucket",
|
||||||
"p2p/dandelion-tower",
|
"p2p/dandelion-tower",
|
||||||
"p2p/async-buffer",
|
"p2p/async-buffer",
|
||||||
"p2p/address-book",
|
"p2p/address-book",
|
||||||
|
|
||||||
|
# Storage
|
||||||
"storage/blockchain",
|
"storage/blockchain",
|
||||||
"storage/service",
|
"storage/service",
|
||||||
"storage/txpool",
|
"storage/txpool",
|
||||||
"storage/database",
|
"storage/database",
|
||||||
"pruning",
|
|
||||||
"test-utils",
|
# RPC
|
||||||
"types",
|
|
||||||
"rpc/json-rpc",
|
"rpc/json-rpc",
|
||||||
"rpc/types",
|
"rpc/types",
|
||||||
"rpc/interface",
|
"rpc/interface",
|
||||||
|
|
||||||
|
# ZMQ
|
||||||
|
"zmq/types",
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
"constants",
|
||||||
|
"cryptonight",
|
||||||
|
"helper",
|
||||||
|
"pruning",
|
||||||
|
"test-utils",
|
||||||
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
lto = true # Build with LTO
|
lto = true # Build with LTO
|
||||||
strip = "none" # Keep panic stack traces
|
strip = "none" # Keep panic stack traces
|
||||||
codegen-units = 1 # Optimize for binary speed over compile times
|
codegen-units = 1 # Optimize for binary speed over compile times
|
||||||
|
@ -52,33 +75,36 @@ opt-level = 3
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Cuprate members
|
# Cuprate members
|
||||||
cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false }
|
cuprate-benchmark-lib = { path = "benches/benchmark/lib", default-features = false }
|
||||||
cuprate-consensus-rules = { path = "consensus/rules", default-features = false }
|
cuprate-benchmark-example = { path = "benches/benchmark/example", default-features = false }
|
||||||
cuprate-constants = { path = "constants", default-features = false }
|
cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false }
|
||||||
cuprate-consensus = { path = "consensus", default-features = false }
|
cuprate-consensus-rules = { path = "consensus/rules", default-features = false }
|
||||||
cuprate-consensus-context = { path = "consensus/context", default-features = false }
|
cuprate-constants = { path = "constants", default-features = false }
|
||||||
cuprate-cryptonight = { path = "cryptonight", default-features = false }
|
cuprate-consensus = { path = "consensus", default-features = false }
|
||||||
cuprate-helper = { path = "helper", default-features = false }
|
cuprate-consensus-context = { path = "consensus/context", default-features = false }
|
||||||
cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false }
|
cuprate-cryptonight = { path = "cryptonight", default-features = false }
|
||||||
cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false }
|
cuprate-helper = { path = "helper", default-features = false }
|
||||||
cuprate-levin = { path = "net/levin", default-features = false }
|
cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false }
|
||||||
cuprate-wire = { path = "net/wire", default-features = false }
|
cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false }
|
||||||
cuprate-p2p = { path = "p2p/p2p", default-features = false }
|
cuprate-levin = { path = "net/levin", default-features = false }
|
||||||
cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false }
|
cuprate-wire = { path = "net/wire", default-features = false }
|
||||||
cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false }
|
cuprate-p2p = { path = "p2p/p2p", default-features = false }
|
||||||
cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false }
|
cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false }
|
||||||
cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false }
|
cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false }
|
||||||
cuprate-address-book = { path = "p2p/address-book", default-features = false }
|
cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false }
|
||||||
cuprate-blockchain = { path = "storage/blockchain", default-features = false }
|
cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false }
|
||||||
cuprate-database = { path = "storage/database", default-features = false }
|
cuprate-address-book = { path = "p2p/address-book", default-features = false }
|
||||||
cuprate-database-service = { path = "storage/service", default-features = false }
|
cuprate-blockchain = { path = "storage/blockchain", default-features = false }
|
||||||
cuprate-txpool = { path = "storage/txpool", default-features = false }
|
cuprate-database = { path = "storage/database", default-features = false }
|
||||||
cuprate-pruning = { path = "pruning", default-features = false }
|
cuprate-database-service = { path = "storage/service", default-features = false }
|
||||||
cuprate-test-utils = { path = "test-utils", default-features = false }
|
cuprate-txpool = { path = "storage/txpool", default-features = false }
|
||||||
cuprate-types = { path = "types", default-features = false }
|
cuprate-pruning = { path = "pruning", default-features = false }
|
||||||
cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false }
|
cuprate-test-utils = { path = "test-utils", default-features = false }
|
||||||
cuprate-rpc-types = { path = "rpc/types", default-features = false }
|
cuprate-types = { path = "types", default-features = false }
|
||||||
cuprate-rpc-interface = { path = "rpc/interface", default-features = false }
|
cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false }
|
||||||
|
cuprate-rpc-types = { path = "rpc/types", default-features = false }
|
||||||
|
cuprate-rpc-interface = { path = "rpc/interface", default-features = false }
|
||||||
|
cuprate-zmq-types = { path = "zmq/types", default-features = false }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { version = "1", default-features = false }
|
anyhow = { version = "1", default-features = false }
|
||||||
|
@ -119,10 +145,13 @@ tokio-util = { version = "0.7", default-features = false }
|
||||||
tokio-stream = { version = "0.1", default-features = false }
|
tokio-stream = { version = "0.1", default-features = false }
|
||||||
tokio = { version = "1", default-features = false }
|
tokio = { version = "1", default-features = false }
|
||||||
tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # <https://github.com/tower-rs/tower/pull/796>
|
tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # <https://github.com/tower-rs/tower/pull/796>
|
||||||
|
toml = { version = "0.8", default-features = false }
|
||||||
tracing-subscriber = { version = "0.3", default-features = false }
|
tracing-subscriber = { version = "0.3", default-features = false }
|
||||||
tracing = { version = "0.1", default-features = false }
|
tracing = { version = "0.1", default-features = false }
|
||||||
|
|
||||||
## workspace.dev-dependencies
|
## workspace.dev-dependencies
|
||||||
|
criterion = { version = "0.5" }
|
||||||
|
function_name = { version = "0.3" }
|
||||||
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
||||||
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
||||||
tempfile = { version = "3" }
|
tempfile = { version = "3" }
|
||||||
|
@ -252,6 +281,9 @@ rest_pat_in_fully_bound_structs = "deny"
|
||||||
redundant_type_annotations = "deny"
|
redundant_type_annotations = "deny"
|
||||||
infinite_loop = "deny"
|
infinite_loop = "deny"
|
||||||
zero_repeat_side_effects = "deny"
|
zero_repeat_side_effects = "deny"
|
||||||
|
non_zero_suggestions = "deny"
|
||||||
|
manual_is_power_of_two = "deny"
|
||||||
|
used_underscore_items = "deny"
|
||||||
|
|
||||||
# Warm
|
# Warm
|
||||||
cast_possible_truncation = "deny"
|
cast_possible_truncation = "deny"
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
# TODO
|
# Benches
|
||||||
|
This directory contains Cuprate's benchmarks and benchmarking utilities.
|
||||||
|
|
||||||
|
See the [`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html)
|
||||||
|
to see how to create and run these benchmarks.
|
43
benches/benchmark/bin/Cargo.toml
Normal file
43
benches/benchmark/bin/Cargo.toml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
[package]
|
||||||
|
name = "cuprate-benchmark"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Cuprate's benchmarking binary"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["hinto-janai"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin"
|
||||||
|
keywords = ["cuprate", "benchmarking", "binary"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# All new benchmarks should be added here!
|
||||||
|
all = ["example"]
|
||||||
|
|
||||||
|
# Non-benchmark features.
|
||||||
|
default = []
|
||||||
|
json = []
|
||||||
|
trace = []
|
||||||
|
debug = []
|
||||||
|
warn = []
|
||||||
|
info = []
|
||||||
|
error = []
|
||||||
|
|
||||||
|
# Benchmark features.
|
||||||
|
# New benchmarks should be added here!
|
||||||
|
example = [
|
||||||
|
"dep:cuprate-benchmark-example"
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cuprate-benchmark-lib = { workspace = true }
|
||||||
|
cuprate-benchmark-example = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
cfg-if = { workspace = true }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json = { workspace = true, features = ["std"] }
|
||||||
|
tracing = { workspace = true, features = ["std", "attributes"] }
|
||||||
|
tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
27
benches/benchmark/bin/README.md
Normal file
27
benches/benchmark/bin/README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
## `cuprate-benchmark`
|
||||||
|
This crate links all benchmarks together into a single binary that can be run as: `cuprate-benchmark`.
|
||||||
|
|
||||||
|
`cuprate-benchmark` will run all enabled benchmarks sequentially and print data at the end.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
Benchmarks are opt-in and enabled via features.
|
||||||
|
|
||||||
|
| Feature | Enables which benchmark crate? |
|
||||||
|
|----------|--------------------------------|
|
||||||
|
| example | cuprate-benchmark-example |
|
||||||
|
| database | cuprate-benchmark-database |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
These are features that aren't for enabling benchmarks, but rather for other things.
|
||||||
|
|
||||||
|
Since `cuprate-benchmark` is built right before it is ran,
|
||||||
|
these features almost act like command line arguments.
|
||||||
|
|
||||||
|
| Features | Does what |
|
||||||
|
|----------|-----------|
|
||||||
|
| json | Prints JSON timings instead of a markdown table
|
||||||
|
| trace | Use the `trace` log-level
|
||||||
|
| debug | Use the `debug` log-level
|
||||||
|
| warn | Use the `warn` log-level
|
||||||
|
| info | Use the `info` log-level (default)
|
||||||
|
| error | Use the `error` log-level
|
29
benches/benchmark/bin/src/log.rs
Normal file
29
benches/benchmark/bin/src/log.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use tracing::{info, instrument, Level};
|
||||||
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
|
/// Initializes the `tracing` logger.
|
||||||
|
#[instrument]
|
||||||
|
pub(crate) fn init_logger() {
|
||||||
|
const LOG_LEVEL: Level = {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "trace")] {
|
||||||
|
Level::TRACE
|
||||||
|
} else if #[cfg(feature = "debug")] {
|
||||||
|
Level::DEBUG
|
||||||
|
} else if #[cfg(feature = "warn")] {
|
||||||
|
Level::WARN
|
||||||
|
} else if #[cfg(feature = "info")] {
|
||||||
|
Level::INFO
|
||||||
|
} else if #[cfg(feature = "error")] {
|
||||||
|
Level::ERROR
|
||||||
|
} else {
|
||||||
|
Level::INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FmtSubscriber::builder().with_max_level(LOG_LEVEL).init();
|
||||||
|
|
||||||
|
info!("Log level: {LOG_LEVEL}");
|
||||||
|
}
|
49
benches/benchmark/bin/src/main.rs
Normal file
49
benches/benchmark/bin/src/main.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![allow(
|
||||||
|
unused_crate_dependencies,
|
||||||
|
reason = "this crate imports many potentially unused dependencies"
|
||||||
|
)]
|
||||||
|
|
||||||
|
mod log;
|
||||||
|
mod print;
|
||||||
|
mod run;
|
||||||
|
mod timings;
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
/// What `main()` does:
|
||||||
|
/// 1. Run all enabled benchmarks
|
||||||
|
/// 2. Record benchmark timings
|
||||||
|
/// 3. Print timing data
|
||||||
|
///
|
||||||
|
/// To add a new benchmark to be ran here:
|
||||||
|
/// 1. Copy + paste a `cfg_if` block
|
||||||
|
/// 2. Change it to your benchmark's feature flag
|
||||||
|
/// 3. Change it to your benchmark's type
|
||||||
|
#[allow(
|
||||||
|
clippy::allow_attributes,
|
||||||
|
unused_variables,
|
||||||
|
unused_mut,
|
||||||
|
unreachable_code,
|
||||||
|
reason = "clippy does not account for all cfg()s"
|
||||||
|
)]
|
||||||
|
fn main() {
|
||||||
|
log::init_logger();
|
||||||
|
|
||||||
|
let mut timings = timings::Timings::new();
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(not(any(feature = "example")))] {
|
||||||
|
println!("No feature specified. Use `--features $BENCHMARK_FEATURE` when building.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "example")] {
|
||||||
|
run::run_benchmark::<cuprate_benchmark_example::Example>(&mut timings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print::print_timings(&timings);
|
||||||
|
}
|
38
benches/benchmark/bin/src/print.rs
Normal file
38
benches/benchmark/bin/src/print.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#![expect(dead_code, reason = "code hidden behind feature flags")]
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
use crate::timings::Timings;
|
||||||
|
|
||||||
|
/// Print the final the final markdown table of benchmark timings.
|
||||||
|
pub(crate) fn print_timings(timings: &Timings) {
|
||||||
|
println!("\nFinished all benchmarks, printing results:");
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "json")] {
|
||||||
|
print_timings_json(timings);
|
||||||
|
} else {
|
||||||
|
print_timings_markdown(timings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default timing formatting.
|
||||||
|
pub(crate) fn print_timings_markdown(timings: &Timings) {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push_str("| Benchmark | Time (seconds) |\n");
|
||||||
|
s.push_str("|------------------------------------|----------------|");
|
||||||
|
|
||||||
|
#[expect(clippy::iter_over_hash_type)]
|
||||||
|
for (k, v) in timings {
|
||||||
|
s += &format!("\n| {k:<34} | {v:<14} |");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n{s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enabled via `json` feature.
|
||||||
|
pub(crate) fn print_timings_json(timings: &Timings) {
|
||||||
|
let json = serde_json::to_string_pretty(timings).unwrap();
|
||||||
|
println!("\n{json}");
|
||||||
|
}
|
36
benches/benchmark/bin/src/run.rs
Normal file
36
benches/benchmark/bin/src/run.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use tracing::{info, instrument, trace};
|
||||||
|
|
||||||
|
use cuprate_benchmark_lib::Benchmark;
|
||||||
|
|
||||||
|
use crate::timings::Timings;
|
||||||
|
|
||||||
|
/// Run a [`Benchmark`] and record its timing.
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub(crate) fn run_benchmark<B: Benchmark>(timings: &mut Timings) {
|
||||||
|
// Get the benchmark name.
|
||||||
|
let name = B::name();
|
||||||
|
trace!("Running benchmark: {name}");
|
||||||
|
|
||||||
|
// Setup the benchmark input.
|
||||||
|
let input = B::SETUP();
|
||||||
|
|
||||||
|
// Sleep before running the benchmark.
|
||||||
|
trace!("Pre-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
|
||||||
|
std::thread::sleep(B::PRE_SLEEP_DURATION);
|
||||||
|
|
||||||
|
// Run/time the benchmark.
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
B::MAIN(input);
|
||||||
|
let time = now.elapsed().as_secs_f32();
|
||||||
|
|
||||||
|
// Print the benchmark timings.
|
||||||
|
info!("{name:>34} ... {time}");
|
||||||
|
assert!(
|
||||||
|
timings.insert(name, time).is_none(),
|
||||||
|
"There were 2 benchmarks with the same name - this collides the final output: {name}",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sleep for a cooldown period after the benchmark run.
|
||||||
|
trace!("Post-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
|
||||||
|
std::thread::sleep(B::POST_SLEEP_DURATION);
|
||||||
|
}
|
5
benches/benchmark/bin/src/timings.rs
Normal file
5
benches/benchmark/bin/src/timings.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/// Benchmark timing data.
|
||||||
|
///
|
||||||
|
/// - Key = benchmark name
|
||||||
|
/// - Value = benchmark time in seconds
|
||||||
|
pub(crate) type Timings = std::collections::HashMap<&'static str, f32>;
|
17
benches/benchmark/example/Cargo.toml
Normal file
17
benches/benchmark/example/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "cuprate-benchmark-example"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Example showcasing Cuprate's benchmarking harness"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["hinto-janai"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example"
|
||||||
|
keywords = ["cuprate", "benchmarking", "example"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cuprate-benchmark-lib = { path = "../lib" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
3
benches/benchmark/example/README.md
Normal file
3
benches/benchmark/example/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## `cuprate-benchmark-example`
|
||||||
|
This crate contains a short example benchmark that shows how to implement and use
|
||||||
|
`cuprate-benchmark-lib` so that it can be ran by `cuprate-benchmark`.
|
42
benches/benchmark/example/src/lib.rs
Normal file
42
benches/benchmark/example/src/lib.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
use std::hint::black_box;
|
||||||
|
|
||||||
|
use cuprate_benchmark_lib::Benchmark;
|
||||||
|
|
||||||
|
/// Marker struct that implements [`Benchmark`]
|
||||||
|
pub struct Example;
|
||||||
|
|
||||||
|
/// The input to our benchmark function.
|
||||||
|
pub type ExampleBenchmarkInput = u64;
|
||||||
|
|
||||||
|
/// The setup function that creates the input.
|
||||||
|
pub const fn example_benchmark_setup() -> ExampleBenchmarkInput {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main benchmarking function.
|
||||||
|
#[expect(clippy::unit_arg)]
|
||||||
|
pub fn example_benchmark_main(input: ExampleBenchmarkInput) {
|
||||||
|
// In this case, we're simply benchmarking the
|
||||||
|
// performance of simple arithmetic on the input data.
|
||||||
|
|
||||||
|
fn math(input: ExampleBenchmarkInput, number: u64) {
|
||||||
|
let x = input;
|
||||||
|
let x = black_box(x * number);
|
||||||
|
let x = black_box(x / number);
|
||||||
|
let x = black_box(x + number);
|
||||||
|
let _ = black_box(x - number);
|
||||||
|
}
|
||||||
|
|
||||||
|
for number in 1..100_000_000 {
|
||||||
|
black_box(math(input, number));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implementation will be run by `cuprate-benchmark`.
|
||||||
|
impl Benchmark for Example {
|
||||||
|
type Input = ExampleBenchmarkInput;
|
||||||
|
const SETUP: fn() -> Self::Input = example_benchmark_setup;
|
||||||
|
const MAIN: fn(Self::Input) = example_benchmark_main;
|
||||||
|
}
|
18
benches/benchmark/lib/Cargo.toml
Normal file
18
benches/benchmark/lib/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "cuprate-benchmark-lib"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Cuprate's benchmarking library"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["hinto-janai"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib"
|
||||||
|
keywords = ["cuprate", "benchmarking", "library"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
15
benches/benchmark/lib/README.md
Normal file
15
benches/benchmark/lib/README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
## `cuprate-benchmark-lib`
|
||||||
|
This crate is the glue between
|
||||||
|
[`cuprate-benchmark`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/bin)
|
||||||
|
and all the benchmark crates.
|
||||||
|
|
||||||
|
It defines the [`crate::Benchmark`] trait, which is the behavior of all benchmarks.
|
||||||
|
|
||||||
|
See the [`cuprate-benchmark-example`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/example)
|
||||||
|
crate to see an example implementation of this trait.
|
||||||
|
|
||||||
|
After implementing this trait, a few steps must
|
||||||
|
be done such that the `cuprate-benchmark` binary
|
||||||
|
can actually run your benchmark crate; see the
|
||||||
|
[`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html)
|
||||||
|
to see how to do this.
|
45
benches/benchmark/lib/src/benchmark.rs
Normal file
45
benches/benchmark/lib/src/benchmark.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//! Benchmarking trait.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// A benchmarking function and its inputs.
|
||||||
|
pub trait Benchmark {
|
||||||
|
/// The benchmark's name.
|
||||||
|
///
|
||||||
|
/// This is automatically implemented
|
||||||
|
/// as the name of the [`Self`] type.
|
||||||
|
//
|
||||||
|
// FIXME: use `const` instead of `fn` when stable
|
||||||
|
// <https://github.com/rust-lang/rust/issues/63084>
|
||||||
|
fn name() -> &'static str {
|
||||||
|
std::any::type_name::<Self>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input to the main benchmarking function.
|
||||||
|
///
|
||||||
|
/// This is passed to [`Self::MAIN`].
|
||||||
|
type Input;
|
||||||
|
|
||||||
|
/// Setup function to generate the input.
|
||||||
|
///
|
||||||
|
/// This function is not timed.
|
||||||
|
const SETUP: fn() -> Self::Input;
|
||||||
|
|
||||||
|
/// The main function to benchmark.
|
||||||
|
///
|
||||||
|
/// The start of the timer begins right before
|
||||||
|
/// this function is called and ends after the
|
||||||
|
/// function returns.
|
||||||
|
const MAIN: fn(Self::Input);
|
||||||
|
|
||||||
|
/// `cuprate-benchmark` will sleep for this [`Duration`] after
|
||||||
|
/// creating the [`Self::Input`], but before starting [`Self::MAIN`].
|
||||||
|
///
|
||||||
|
/// 1 second by default.
|
||||||
|
const PRE_SLEEP_DURATION: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
/// `cuprate-benchmark` will sleep for this [`Duration`] after [`Self::MAIN`].
|
||||||
|
///
|
||||||
|
/// 1 second by default.
|
||||||
|
const POST_SLEEP_DURATION: Duration = Duration::from_secs(1);
|
||||||
|
}
|
5
benches/benchmark/lib/src/lib.rs
Normal file
5
benches/benchmark/lib/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
mod benchmark;
|
||||||
|
|
||||||
|
pub use benchmark::Benchmark;
|
23
benches/criterion/cuprate-json-rpc/Cargo.toml
Normal file
23
benches/criterion/cuprate-json-rpc/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "cuprate-criterion-json-rpc"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Criterion benchmarking for cuprate-json-rpc"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["hinto-janai"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc"
|
||||||
|
keywords = ["cuprate", "json-rpc", "criterion", "benchmark"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cuprate-json-rpc = { workspace = true }
|
||||||
|
|
||||||
|
criterion = { workspace = true }
|
||||||
|
function_name = { workspace = true }
|
||||||
|
serde_json = { workspace = true, features = ["default"] }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "main"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
8
benches/criterion/cuprate-json-rpc/benches/main.rs
Normal file
8
benches/criterion/cuprate-json-rpc/benches/main.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//! Benchmarks for `cuprate-json-rpc`.
|
||||||
|
#![allow(unused_crate_dependencies)]
|
||||||
|
|
||||||
|
mod response;
|
||||||
|
|
||||||
|
criterion::criterion_main! {
|
||||||
|
response::serde,
|
||||||
|
}
|
110
benches/criterion/cuprate-json-rpc/benches/response.rs
Normal file
110
benches/criterion/cuprate-json-rpc/benches/response.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
//! Benchmarks for [`Response`].
|
||||||
|
#![allow(unused_attributes, unused_crate_dependencies)]
|
||||||
|
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use function_name::named;
|
||||||
|
use serde_json::{from_str, to_string_pretty};
|
||||||
|
|
||||||
|
use cuprate_json_rpc::{Id, Response};
|
||||||
|
|
||||||
|
// `serde` benchmarks on `Response`.
|
||||||
|
//
|
||||||
|
// These are benchmarked as `Response` has a custom serde implementation.
|
||||||
|
criterion_group! {
|
||||||
|
name = serde;
|
||||||
|
config = Criterion::default();
|
||||||
|
targets =
|
||||||
|
response_from_str_u8,
|
||||||
|
response_from_str_u64,
|
||||||
|
response_from_str_string_5_len,
|
||||||
|
response_from_str_string_10_len,
|
||||||
|
response_from_str_string_100_len,
|
||||||
|
response_from_str_string_500_len,
|
||||||
|
response_to_string_pretty_u8,
|
||||||
|
response_to_string_pretty_u64,
|
||||||
|
response_to_string_pretty_string_5_len,
|
||||||
|
response_to_string_pretty_string_10_len,
|
||||||
|
response_to_string_pretty_string_100_len,
|
||||||
|
response_to_string_pretty_string_500_len,
|
||||||
|
response_from_str_bad_field_1,
|
||||||
|
response_from_str_bad_field_5,
|
||||||
|
response_from_str_bad_field_10,
|
||||||
|
response_from_str_bad_field_100,
|
||||||
|
response_from_str_missing_field,
|
||||||
|
}
|
||||||
|
criterion_main!(serde);
|
||||||
|
|
||||||
|
/// Generate `from_str` deserialization benchmark functions for [`Response`].
|
||||||
|
macro_rules! impl_from_str_benchmark {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$fn_name:ident => $request_type:ty => $request_string:literal,
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[named]
|
||||||
|
fn $fn_name(c: &mut Criterion) {
|
||||||
|
let request_string = $request_string;
|
||||||
|
|
||||||
|
c.bench_function(function_name!(), |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let _r = from_str::<Response<$request_type>>(
|
||||||
|
black_box(request_string)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from_str_benchmark! {
|
||||||
|
response_from_str_u8 => u8 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
response_from_str_u64 => u64 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
response_from_str_string_5_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hello"}"#,
|
||||||
|
response_from_str_string_10_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hellohello"}"#,
|
||||||
|
response_from_str_string_100_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#,
|
||||||
|
response_from_str_string_500_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#,
|
||||||
|
|
||||||
|
// The custom serde currently looks at all fields.
|
||||||
|
// These are for testing the performance if the serde
|
||||||
|
// has to parse through a bunch of unrelated fields.
|
||||||
|
response_from_str_bad_field_1 => u8 => r#"{"bad_field":0,"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
response_from_str_bad_field_5 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
response_from_str_bad_field_10 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"bad_field_6":0,"bad_field_7":0,"bad_field_8":0,"bad_field_9":0,"bad_field_10":0,"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
response_from_str_bad_field_100 => u8 => r#"{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"jsonrpc":"2.0","id":123,"result":0}"#,
|
||||||
|
|
||||||
|
// These are missing the `jsonrpc` field.
|
||||||
|
response_from_str_missing_field => u8 => r#"{"id":123,"result":0}"#,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate `to_string_pretty` serialization benchmark functions for [`Response`].
|
||||||
|
macro_rules! impl_to_string_pretty_benchmark {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$fn_name:ident => $request_constructor:expr_2021,
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[named]
|
||||||
|
fn $fn_name(c: &mut Criterion) {
|
||||||
|
let request = $request_constructor;
|
||||||
|
|
||||||
|
c.bench_function(function_name!(), |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let _s = to_string_pretty(black_box(&request)).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_to_string_pretty_benchmark! {
|
||||||
|
response_to_string_pretty_u8 => Response::<u8>::ok(Id::Null, 0),
|
||||||
|
response_to_string_pretty_u64 => Response::<u64>::ok(Id::Null, 0),
|
||||||
|
response_to_string_pretty_string_5_len => Response::ok(Id::Null, String::from("hello")),
|
||||||
|
response_to_string_pretty_string_10_len => Response::ok(Id::Null, String::from("hellohello")),
|
||||||
|
response_to_string_pretty_string_100_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")),
|
||||||
|
response_to_string_pretty_string_500_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")),
|
||||||
|
}
|
2
benches/criterion/cuprate-json-rpc/src/lib.rs
Normal file
2
benches/criterion/cuprate-json-rpc/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
//! Benchmark lib for `cuprate-json-rpc`.
|
||||||
|
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]
|
21
benches/criterion/example/Cargo.toml
Normal file
21
benches/criterion/example/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "cuprate-criterion-example"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Criterion benchmarking example for Cuprate"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["hinto-janai"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example"
|
||||||
|
keywords = ["cuprate", "criterion", "benchmark", "example"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
criterion = { workspace = true }
|
||||||
|
function_name = { workspace = true }
|
||||||
|
serde_json = { workspace = true, features = ["default"] }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "main"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
14
benches/criterion/example/README.md
Normal file
14
benches/criterion/example/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
## `cuprate-criterion-example`
|
||||||
|
An example of using Criterion for benchmarking Cuprate crates.
|
||||||
|
|
||||||
|
Consider copy+pasting this crate to use as a base when creating new Criterion benchmark crates.
|
||||||
|
|
||||||
|
## `src/`
|
||||||
|
Benchmark crates have a `benches/` ran by `cargo bench`, but they are also crates themselves,
|
||||||
|
as in, they have a `src` folder that `benches/` can pull code from.
|
||||||
|
|
||||||
|
The `src` directories in these benchmarking crates are usually filled with
|
||||||
|
helper functions, types, etc, that are used repeatedly in the benchmarks.
|
||||||
|
|
||||||
|
## `benches/`
|
||||||
|
These are the actual benchmarks ran by `cargo bench`.
|
48
benches/criterion/example/benches/example.rs
Normal file
48
benches/criterion/example/benches/example.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
//! Benchmarks.
|
||||||
|
#![allow(unused_attributes, unused_crate_dependencies)]
|
||||||
|
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
|
use function_name::named;
|
||||||
|
|
||||||
|
use cuprate_criterion_example::SomeHardToCreateObject;
|
||||||
|
|
||||||
|
// This is how you register criterion benchmarks.
|
||||||
|
criterion_group! {
|
||||||
|
name = benches;
|
||||||
|
config = Criterion::default();
|
||||||
|
targets = benchmark_1, benchmark_range,
|
||||||
|
}
|
||||||
|
criterion_main!(benches);
|
||||||
|
|
||||||
|
/// Benchmark a single input.
|
||||||
|
///
|
||||||
|
/// <https://bheisler.github.io/criterion.rs/book/user_guide/benchmarking_with_inputs.html#benchmarking-with-one-input>
|
||||||
|
#[named]
|
||||||
|
fn benchmark_1(c: &mut Criterion) {
|
||||||
|
// It is recommended to use `function_name!()` as a benchmark
|
||||||
|
// identifier instead of manually re-typing the function name.
|
||||||
|
c.bench_function(function_name!(), |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(SomeHardToCreateObject::from(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Benchmark a range of inputs.
|
||||||
|
///
|
||||||
|
/// <https://bheisler.github.io/criterion.rs/book/user_guide/benchmarking_with_inputs.html#benchmarking-with-a-range-of-values>
|
||||||
|
#[named]
|
||||||
|
fn benchmark_range(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group(function_name!());
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
group.throughput(Throughput::Elements(i));
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(i), &i, |b, &i| {
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(SomeHardToCreateObject::from(i));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
10
benches/criterion/example/benches/main.rs
Normal file
10
benches/criterion/example/benches/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! Benchmarks examples.
|
||||||
|
#![allow(unused_crate_dependencies)]
|
||||||
|
|
||||||
|
// All modules within `benches/` are `mod`ed here.
|
||||||
|
mod example;
|
||||||
|
|
||||||
|
// And all the Criterion benchmarks are registered like so:
|
||||||
|
criterion::criterion_main! {
|
||||||
|
example::benches,
|
||||||
|
}
|
13
benches/criterion/example/src/lib.rs
Normal file
13
benches/criterion/example/src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![doc = include_str!("../README.md")] // See the README for crate documentation.
|
||||||
|
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]
|
||||||
|
|
||||||
|
/// Shared type that all benchmarks can use.
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub struct SomeHardToCreateObject(u64);
|
||||||
|
|
||||||
|
impl From<u64> for SomeHardToCreateObject {
|
||||||
|
/// Shared function that all benchmarks can use.
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
name = "cuprated"
|
name = "cuprated"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "The Cuprate Monero Rust node."
|
description = "The Cuprate Rust Monero node."
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
authors = ["Boog900", "hinto-janai", "SyntheticBird45"]
|
authors = ["Boog900", "hinto-janai", "SyntheticBird45"]
|
||||||
repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
|
repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
|
||||||
|
@ -12,29 +12,29 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
|
||||||
cuprate-consensus = { workspace = true }
|
cuprate-consensus = { workspace = true }
|
||||||
cuprate-fast-sync = { workspace = true }
|
cuprate-fast-sync = { workspace = true }
|
||||||
cuprate-consensus-context = { workspace = true }
|
cuprate-consensus-context = { workspace = true }
|
||||||
cuprate-consensus-rules = { workspace = true }
|
cuprate-consensus-rules = { workspace = true }
|
||||||
cuprate-constants = { workspace = true }
|
cuprate-cryptonight = { workspace = true }
|
||||||
cuprate-cryptonight = { workspace = true }
|
cuprate-helper = { workspace = true, features = ["serde"] }
|
||||||
cuprate-helper = { workspace = true }
|
cuprate-epee-encoding = { workspace = true }
|
||||||
cuprate-epee-encoding = { workspace = true }
|
cuprate-fixed-bytes = { workspace = true }
|
||||||
cuprate-fixed-bytes = { workspace = true }
|
cuprate-levin = { workspace = true }
|
||||||
cuprate-levin = { workspace = true }
|
cuprate-wire = { workspace = true }
|
||||||
cuprate-wire = { workspace = true }
|
cuprate-p2p = { workspace = true }
|
||||||
cuprate-p2p = { workspace = true }
|
cuprate-p2p-core = { workspace = true }
|
||||||
cuprate-p2p-core = { workspace = true }
|
cuprate-dandelion-tower = { workspace = true, features = ["txpool"] }
|
||||||
cuprate-dandelion-tower = { workspace = true, features = ["txpool"] }
|
cuprate-async-buffer = { workspace = true }
|
||||||
cuprate-async-buffer = { workspace = true }
|
cuprate-address-book = { workspace = true }
|
||||||
cuprate-address-book = { workspace = true }
|
cuprate-blockchain = { workspace = true }
|
||||||
cuprate-blockchain = { workspace = true }
|
cuprate-database-service = { workspace = true, features = ["serde"] }
|
||||||
cuprate-database-service = { workspace = true }
|
cuprate-txpool = { workspace = true }
|
||||||
cuprate-txpool = { workspace = true }
|
cuprate-database = { workspace = true, features = ["serde"] }
|
||||||
cuprate-database = { workspace = true }
|
cuprate-pruning = { workspace = true }
|
||||||
cuprate-pruning = { workspace = true }
|
cuprate-test-utils = { workspace = true }
|
||||||
cuprate-test-utils = { workspace = true }
|
cuprate-types = { workspace = true }
|
||||||
cuprate-types = { workspace = true }
|
cuprate-json-rpc = { workspace = true }
|
||||||
cuprate-json-rpc = { workspace = true }
|
cuprate-rpc-interface = { workspace = true }
|
||||||
cuprate-rpc-interface = { workspace = true }
|
cuprate-rpc-types = { workspace = true }
|
||||||
cuprate-rpc-types = { workspace = true }
|
|
||||||
|
|
||||||
# TODO: after v1.0.0, remove unneeded dependencies.
|
# TODO: after v1.0.0, remove unneeded dependencies.
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
@ -44,7 +44,7 @@ borsh = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
bytes = { workspace = true }
|
bytes = { workspace = true }
|
||||||
cfg-if = { workspace = true }
|
cfg-if = { workspace = true }
|
||||||
clap = { workspace = true, features = ["cargo"] }
|
clap = { workspace = true, features = ["cargo", "help", "wrap_help"] }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
crypto-bigint = { workspace = true }
|
crypto-bigint = { workspace = true }
|
||||||
crossbeam = { workspace = true }
|
crossbeam = { workspace = true }
|
||||||
|
@ -71,15 +71,10 @@ thread_local = { workspace = true }
|
||||||
tokio-util = { workspace = true }
|
tokio-util = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
toml = { workspace = true, features = ["parse", "display"]}
|
||||||
tower = { workspace = true }
|
tower = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] }
|
tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true, features = ["default"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
|
|
67
binaries/cuprated/Cuprated.toml
Normal file
67
binaries/cuprated/Cuprated.toml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# ____ _
|
||||||
|
# / ___| _ _ __ _ __ __ _| |_ ___
|
||||||
|
# | | | | | | '_ \| '__/ _` | __/ _ \
|
||||||
|
# | |__| |_| | |_) | | | (_| | || __/
|
||||||
|
# \____\__,_| .__/|_| \__,_|\__\___|
|
||||||
|
# |_|
|
||||||
|
#
|
||||||
|
|
||||||
|
## The network to run on, valid values: "Mainnet", "Testnet", "Stagenet".
|
||||||
|
network = "Mainnet"
|
||||||
|
|
||||||
|
## Tracing config.
|
||||||
|
[tracing]
|
||||||
|
## The minimum level for log events to be displayed.
|
||||||
|
level = "info"
|
||||||
|
|
||||||
|
## Clear-net config.
|
||||||
|
[p2p.clear_net]
|
||||||
|
## The number of outbound connections we should make and maintain.
|
||||||
|
outbound_connections = 64
|
||||||
|
## The number of extra connections we should make under load from the rest of Cuprate, i.e. when syncing.
|
||||||
|
extra_outbound_connections = 8
|
||||||
|
## The maximum number of incoming we should allow.
|
||||||
|
max_inbound_connections = 128
|
||||||
|
## The percent of outbound connections that should be to nodes we have not connected to before.
|
||||||
|
gray_peers_percent = 0.7
|
||||||
|
## The port to accept connections on, if left `0` no connections will be accepted.
|
||||||
|
p2p_port = 0
|
||||||
|
## The IP address to listen to connections on.
|
||||||
|
listen_on = "0.0.0.0"
|
||||||
|
|
||||||
|
## The Clear-net addressbook config.
|
||||||
|
[p2p.clear_net.address_book_config]
|
||||||
|
## The size of the white peer list, which contains peers we have made a connection to before.
|
||||||
|
max_white_list_length = 1_000
|
||||||
|
## The size of the gray peer list, which contains peers we have not made a connection to before.
|
||||||
|
max_gray_list_length = 5_000
|
||||||
|
## The amount of time between address book saves.
|
||||||
|
peer_save_period = { secs = 90, nanos = 0 }
|
||||||
|
|
||||||
|
## The block downloader config.
|
||||||
|
[p2p.block_downloader]
|
||||||
|
## The size of the buffer of sequential blocks waiting to be verified and added to the chain (bytes).
|
||||||
|
buffer_bytes = 50_000_000
|
||||||
|
## The size of the queue of blocks which are waiting for a parent block to be downloaded (bytes).
|
||||||
|
in_progress_queue_bytes = 50_000_000
|
||||||
|
## The target size of a batch of blocks (bytes), must not exceed 100MB.
|
||||||
|
target_batch_bytes= 5_000_000
|
||||||
|
## The amount of time between checking the pool of connected peers for free peers to download blocks.
|
||||||
|
check_client_pool_interval = { secs = 30, nanos = 0 }
|
||||||
|
|
||||||
|
## Storage config
|
||||||
|
[storage]
|
||||||
|
## The amount of reader threads to spawn.
|
||||||
|
reader_threads = "OnePerThread"
|
||||||
|
|
||||||
|
## Txpool storage config.
|
||||||
|
[storage.txpool]
|
||||||
|
## The database sync mode for the txpool.
|
||||||
|
sync_mode = "Async"
|
||||||
|
## The maximum size of all the txs in the pool (bytes).
|
||||||
|
max_txpool_byte_size = 100_000_000
|
||||||
|
|
||||||
|
## Blockchain storage config.
|
||||||
|
[storage.blockchain]
|
||||||
|
## The database sync mode for the blockchain.
|
||||||
|
sync_mode = "Async"
|
|
@ -12,7 +12,7 @@ use tracing::instrument;
|
||||||
use cuprate_consensus::{BlockChainContext, BlockChainContextRequest, BlockChainContextResponse};
|
use cuprate_consensus::{BlockChainContext, BlockChainContextRequest, BlockChainContextResponse};
|
||||||
use cuprate_p2p::{
|
use cuprate_p2p::{
|
||||||
block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse},
|
block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse},
|
||||||
NetworkInterface,
|
NetworkInterface, PeerSetRequest, PeerSetResponse,
|
||||||
};
|
};
|
||||||
use cuprate_p2p_core::ClearNet;
|
use cuprate_p2p_core::ClearNet;
|
||||||
|
|
||||||
|
@ -28,15 +28,11 @@ pub enum SyncerError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The syncer tasks that makes sure we are fully synchronised with our connected peers.
|
/// The syncer tasks that makes sure we are fully synchronised with our connected peers.
|
||||||
#[expect(
|
|
||||||
clippy::significant_drop_tightening,
|
|
||||||
reason = "Client pool which will be removed"
|
|
||||||
)]
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
pub async fn syncer<C, CN>(
|
pub async fn syncer<C, CN>(
|
||||||
mut context_svc: C,
|
mut context_svc: C,
|
||||||
our_chain: CN,
|
our_chain: CN,
|
||||||
clearnet_interface: NetworkInterface<ClearNet>,
|
mut clearnet_interface: NetworkInterface<ClearNet>,
|
||||||
incoming_block_batch_tx: mpsc::Sender<BlockBatch>,
|
incoming_block_batch_tx: mpsc::Sender<BlockBatch>,
|
||||||
stop_current_block_downloader: Arc<Notify>,
|
stop_current_block_downloader: Arc<Notify>,
|
||||||
block_downloader_config: BlockDownloaderConfig,
|
block_downloader_config: BlockDownloaderConfig,
|
||||||
|
@ -67,8 +63,6 @@ where
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
let client_pool = clearnet_interface.client_pool();
|
|
||||||
|
|
||||||
tracing::debug!("Waiting for new sync info in top sync channel");
|
tracing::debug!("Waiting for new sync info in top sync channel");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -79,9 +73,20 @@ where
|
||||||
check_update_blockchain_context(&mut context_svc, &mut blockchain_ctx).await?;
|
check_update_blockchain_context(&mut context_svc, &mut blockchain_ctx).await?;
|
||||||
let raw_blockchain_context = blockchain_ctx.unchecked_blockchain_context();
|
let raw_blockchain_context = blockchain_ctx.unchecked_blockchain_context();
|
||||||
|
|
||||||
if !client_pool.contains_client_with_more_cumulative_difficulty(
|
let PeerSetResponse::MostPoWSeen {
|
||||||
raw_blockchain_context.cumulative_difficulty,
|
cumulative_difficulty,
|
||||||
) {
|
..
|
||||||
|
} = clearnet_interface
|
||||||
|
.peer_set()
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(PeerSetRequest::MostPoWSeen)
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
if cumulative_difficulty <= raw_blockchain_context.cumulative_difficulty {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1,159 @@
|
||||||
//! cuprated config
|
//! cuprated config
|
||||||
|
use std::{
|
||||||
|
fs::{read_to_string, File},
|
||||||
|
io,
|
||||||
|
path::Path,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use cuprate_consensus::ContextConfig;
|
||||||
|
use cuprate_helper::{
|
||||||
|
fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME},
|
||||||
|
network::Network,
|
||||||
|
};
|
||||||
|
use cuprate_p2p::block_downloader::BlockDownloaderConfig;
|
||||||
|
use cuprate_p2p_core::{ClearNet, ClearNetServerCfg};
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod fs;
|
||||||
|
mod p2p;
|
||||||
|
mod storage;
|
||||||
|
mod tracing_config;
|
||||||
|
|
||||||
|
use crate::config::fs::FileSystemConfig;
|
||||||
|
use p2p::P2PConfig;
|
||||||
|
use storage::StorageConfig;
|
||||||
|
use tracing_config::TracingConfig;
|
||||||
|
|
||||||
|
/// Reads the args & config file, returning a [`Config`].
|
||||||
|
pub fn read_config_and_args() -> Config {
|
||||||
|
let args = args::Args::parse();
|
||||||
|
args.do_quick_requests();
|
||||||
|
|
||||||
|
let config: Config = if let Some(config_file) = &args.config_file {
|
||||||
|
// If a config file was set in the args try to read it and exit if we can't.
|
||||||
|
match Config::read_from_path(config_file) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to read config from file: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// First attempt to read the config file from the current directory.
|
||||||
|
std::env::current_dir()
|
||||||
|
.map(|path| path.join(DEFAULT_CONFIG_FILE_NAME))
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(Config::read_from_path)
|
||||||
|
.inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}"))
|
||||||
|
// otherwise try the main config directory.
|
||||||
|
.or_else(|_| {
|
||||||
|
let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME);
|
||||||
|
Config::read_from_path(file)
|
||||||
|
})
|
||||||
|
.inspect_err(|e| {
|
||||||
|
tracing::debug!("Failed to read config from config dir: {e}");
|
||||||
|
eprintln!("Failed to find/read config file, using default config.");
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
args.apply_args(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The config for all of Cuprate.
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Config {
|
||||||
|
/// The network we should run on.
|
||||||
|
network: Network,
|
||||||
|
|
||||||
|
/// [`tracing`] config.
|
||||||
|
tracing: TracingConfig,
|
||||||
|
|
||||||
|
/// The P2P network config.
|
||||||
|
p2p: P2PConfig,
|
||||||
|
|
||||||
|
/// The storage config.
|
||||||
|
storage: StorageConfig,
|
||||||
|
|
||||||
|
fs: FileSystemConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Attempts to read a config file in [`toml`] format from the given [`Path`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config.
|
||||||
|
fn read_from_path(file: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
|
||||||
|
let file_text = read_to_string(file.as_ref())?;
|
||||||
|
|
||||||
|
Ok(toml::from_str(&file_text)
|
||||||
|
.inspect(|_| eprintln!("Using config at: {}", file.as_ref().to_string_lossy()))
|
||||||
|
.inspect_err(|e| {
|
||||||
|
eprintln!("{e}");
|
||||||
|
eprintln!(
|
||||||
|
"Failed to parse config file at: {}",
|
||||||
|
file.as_ref().to_string_lossy()
|
||||||
|
);
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`Network`] we are running on.
|
||||||
|
pub const fn network(&self) -> Network {
|
||||||
|
self.network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`ClearNet`], [`cuprate_p2p::P2PConfig`].
|
||||||
|
pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig<ClearNet> {
|
||||||
|
cuprate_p2p::P2PConfig {
|
||||||
|
network: self.network,
|
||||||
|
seeds: p2p::clear_net_seed_nodes(self.network),
|
||||||
|
outbound_connections: self.p2p.clear_net.general.outbound_connections,
|
||||||
|
extra_outbound_connections: self.p2p.clear_net.general.extra_outbound_connections,
|
||||||
|
max_inbound_connections: self.p2p.clear_net.general.max_inbound_connections,
|
||||||
|
gray_peers_percent: self.p2p.clear_net.general.gray_peers_percent,
|
||||||
|
server_config: Some(ClearNetServerCfg {
|
||||||
|
ip: self.p2p.clear_net.listen_on,
|
||||||
|
}),
|
||||||
|
p2p_port: self.p2p.clear_net.general.p2p_port,
|
||||||
|
// TODO: set this if a public RPC server is set.
|
||||||
|
rpc_port: 0,
|
||||||
|
address_book_config: self
|
||||||
|
.p2p
|
||||||
|
.clear_net
|
||||||
|
.general
|
||||||
|
.address_book_config(&self.fs.cache_directory, self.network),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`ContextConfig`].
|
||||||
|
pub const fn context_config(&self) -> ContextConfig {
|
||||||
|
match self.network {
|
||||||
|
Network::Mainnet => ContextConfig::main_net(),
|
||||||
|
Network::Stagenet => ContextConfig::stage_net(),
|
||||||
|
Network::Testnet => ContextConfig::test_net(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`cuprate_blockchain`] config.
|
||||||
|
pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
|
||||||
|
let blockchain = &self.storage.blockchain;
|
||||||
|
|
||||||
|
// We don't set reader threads as we manually make the reader threadpool.
|
||||||
|
cuprate_blockchain::config::ConfigBuilder::default()
|
||||||
|
.network(self.network)
|
||||||
|
.data_directory(self.fs.data_directory.clone())
|
||||||
|
.sync_mode(blockchain.shared.sync_mode)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`BlockDownloaderConfig`].
|
||||||
|
pub fn block_downloader_config(&self) -> BlockDownloaderConfig {
|
||||||
|
self.p2p.block_downloader.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
binaries/cuprated/src/config/args.rs
Normal file
55
binaries/cuprated/src/config/args.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use std::{io::Write, path::PathBuf, process::exit};
|
||||||
|
|
||||||
|
use clap::builder::TypedValueParser;
|
||||||
|
|
||||||
|
use cuprate_helper::network::Network;
|
||||||
|
|
||||||
|
use crate::{config::Config, constants::EXAMPLE_CONFIG};
|
||||||
|
|
||||||
|
/// Cuprate Args.
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
#[command(version, about)]
|
||||||
|
pub struct Args {
|
||||||
|
/// The network to run on.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = Network::Mainnet,
|
||||||
|
value_parser = clap::builder::PossibleValuesParser::new(["mainnet", "testnet", "stagenet"])
|
||||||
|
.map(|s| s.parse::<Network>().unwrap()),
|
||||||
|
)]
|
||||||
|
pub network: Network,
|
||||||
|
/// The amount of outbound clear-net connections to maintain.
|
||||||
|
#[arg(long)]
|
||||||
|
pub outbound_connections: Option<usize>,
|
||||||
|
/// The PATH of the `cuprated` config file.
|
||||||
|
#[arg(long)]
|
||||||
|
pub config_file: Option<PathBuf>,
|
||||||
|
/// Generate a config file and print it to stdout.
|
||||||
|
#[arg(long)]
|
||||||
|
pub generate_config: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
/// Complete any quick requests asked for in [`Args`].
|
||||||
|
///
|
||||||
|
/// May cause the process to [`exit`].
|
||||||
|
pub fn do_quick_requests(&self) {
|
||||||
|
if self.generate_config {
|
||||||
|
println!("{EXAMPLE_CONFIG}");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the [`Args`] to the given [`Config`].
|
||||||
|
///
|
||||||
|
/// This may exit the program if a config value was set that requires an early exit.
|
||||||
|
pub const fn apply_args(&self, mut config: Config) -> Config {
|
||||||
|
config.network = self.network;
|
||||||
|
|
||||||
|
if let Some(outbound_connections) = self.outbound_connections {
|
||||||
|
config.p2p.clear_net.general.outbound_connections = outbound_connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
}
|
21
binaries/cuprated/src/config/fs.rs
Normal file
21
binaries/cuprated/src/config/fs.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use cuprate_helper::fs::{CUPRATE_CACHE_DIR, CUPRATE_DATA_DIR};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct FileSystemConfig {
|
||||||
|
pub data_directory: PathBuf,
|
||||||
|
pub cache_directory: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileSystemConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
data_directory: CUPRATE_DATA_DIR.to_path_buf(),
|
||||||
|
cache_directory: CUPRATE_CACHE_DIR.to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
binaries/cuprated/src/config/p2p.rs
Normal file
178
binaries/cuprated/src/config/p2p.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
use std::{
|
||||||
|
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
path::Path,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use cuprate_helper::{fs::address_book_path, network::Network};
|
||||||
|
|
||||||
|
/// P2P config.
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct P2PConfig {
|
||||||
|
/// Clear-net config.
|
||||||
|
pub clear_net: ClearNetConfig,
|
||||||
|
/// Block downloader config.
|
||||||
|
pub block_downloader: BlockDownloaderConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct BlockDownloaderConfig {
|
||||||
|
/// The size in bytes of the buffer between the block downloader and the place which
|
||||||
|
/// is consuming the downloaded blocks.
|
||||||
|
pub buffer_bytes: usize,
|
||||||
|
/// The size of the in progress queue (in bytes) at which we stop requesting more blocks.
|
||||||
|
pub in_progress_queue_bytes: usize,
|
||||||
|
/// The [`Duration`] between checking the client pool for free peers.
|
||||||
|
pub check_client_pool_interval: Duration,
|
||||||
|
/// The target size of a single batch of blocks (in bytes).
|
||||||
|
pub target_batch_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockDownloaderConfig> for cuprate_p2p::block_downloader::BlockDownloaderConfig {
|
||||||
|
fn from(value: BlockDownloaderConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer_bytes: value.buffer_bytes,
|
||||||
|
in_progress_queue_bytes: value.in_progress_queue_bytes,
|
||||||
|
check_client_pool_interval: value.check_client_pool_interval,
|
||||||
|
target_batch_bytes: value.target_batch_bytes,
|
||||||
|
initial_batch_len: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BlockDownloaderConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buffer_bytes: 50_000_000,
|
||||||
|
in_progress_queue_bytes: 50_000_000,
|
||||||
|
check_client_pool_interval: Duration::from_secs(30),
|
||||||
|
target_batch_bytes: 5_000_000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The config values for P2P clear-net.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct ClearNetConfig {
|
||||||
|
/// The server config.
|
||||||
|
pub listen_on: IpAddr,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub general: SharedNetConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClearNetConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
listen_on: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
|
||||||
|
general: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Network config values shared between all network zones.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct SharedNetConfig {
|
||||||
|
/// The number of outbound connections to make and try keep.
|
||||||
|
pub outbound_connections: usize,
|
||||||
|
/// The amount of extra connections we can make if we are under load from the rest of Cuprate.
|
||||||
|
pub extra_outbound_connections: usize,
|
||||||
|
/// The maximum amount of inbound connections
|
||||||
|
pub max_inbound_connections: usize,
|
||||||
|
/// The percent of connections that should be to peers we haven't connected to before.
|
||||||
|
pub gray_peers_percent: f64,
|
||||||
|
/// port to use to accept p2p connections.
|
||||||
|
pub p2p_port: u16,
|
||||||
|
/// The address book config.
|
||||||
|
address_book_config: AddressBookConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedNetConfig {
|
||||||
|
/// Returns the [`AddressBookConfig`].
|
||||||
|
pub fn address_book_config(
|
||||||
|
&self,
|
||||||
|
cache_dir: &Path,
|
||||||
|
network: Network,
|
||||||
|
) -> cuprate_address_book::AddressBookConfig {
|
||||||
|
cuprate_address_book::AddressBookConfig {
|
||||||
|
max_white_list_length: self.address_book_config.max_white_list_length,
|
||||||
|
max_gray_list_length: self.address_book_config.max_gray_list_length,
|
||||||
|
peer_store_directory: address_book_path(cache_dir, network),
|
||||||
|
peer_save_period: self.address_book_config.peer_save_period,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SharedNetConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
outbound_connections: 64,
|
||||||
|
extra_outbound_connections: 8,
|
||||||
|
max_inbound_connections: 128,
|
||||||
|
gray_peers_percent: 0.7,
|
||||||
|
p2p_port: 0,
|
||||||
|
address_book_config: AddressBookConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct AddressBookConfig {
|
||||||
|
max_white_list_length: usize,
|
||||||
|
max_gray_list_length: usize,
|
||||||
|
peer_save_period: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AddressBookConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
max_white_list_length: 1_000,
|
||||||
|
max_gray_list_length: 5_000,
|
||||||
|
peer_save_period: Duration::from_secs(30),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seed nodes for [`ClearNet`](cuprate_p2p_core::ClearNet).
|
||||||
|
pub fn clear_net_seed_nodes(network: Network) -> Vec<SocketAddr> {
|
||||||
|
let seeds = match network {
|
||||||
|
Network::Mainnet => [
|
||||||
|
"176.9.0.187:18080",
|
||||||
|
"88.198.163.90:18080",
|
||||||
|
"66.85.74.134:18080",
|
||||||
|
"51.79.173.165:18080",
|
||||||
|
"192.99.8.110:18080",
|
||||||
|
"37.187.74.171:18080",
|
||||||
|
"77.172.183.193:18080",
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
Network::Stagenet => [
|
||||||
|
"176.9.0.187:38080",
|
||||||
|
"51.79.173.165:38080",
|
||||||
|
"192.99.8.110:38080",
|
||||||
|
"37.187.74.171:38080",
|
||||||
|
"77.172.183.193:38080",
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
Network::Testnet => [
|
||||||
|
"176.9.0.187:28080",
|
||||||
|
"51.79.173.165:28080",
|
||||||
|
"192.99.8.110:28080",
|
||||||
|
"37.187.74.171:28080",
|
||||||
|
"77.172.183.193:28080",
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
};
|
||||||
|
|
||||||
|
seeds
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.parse())
|
||||||
|
.collect::<Result<_, _>>()
|
||||||
|
.unwrap()
|
||||||
|
}
|
67
binaries/cuprated/src/config/storage.rs
Normal file
67
binaries/cuprated/src/config/storage.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use cuprate_database::config::SyncMode;
|
||||||
|
use cuprate_database_service::ReaderThreads;
|
||||||
|
use cuprate_helper::fs::CUPRATE_DATA_DIR;
|
||||||
|
|
||||||
|
/// The storage config.
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct StorageConfig {
|
||||||
|
/// The amount of reader threads to spawn between the tx-pool and blockchain.
|
||||||
|
pub reader_threads: ReaderThreads,
|
||||||
|
/// The tx-pool config.
|
||||||
|
pub txpool: TxpoolConfig,
|
||||||
|
/// The blockchain config.
|
||||||
|
pub blockchain: BlockchainConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The blockchain config.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct BlockchainConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub shared: SharedStorageConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BlockchainConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
shared: SharedStorageConfig {
|
||||||
|
sync_mode: SyncMode::Async,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tx-pool config.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct TxpoolConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub shared: SharedStorageConfig,
|
||||||
|
|
||||||
|
/// The maximum size of the tx-pool.
|
||||||
|
pub max_txpool_byte_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TxpoolConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
shared: SharedStorageConfig {
|
||||||
|
sync_mode: SyncMode::Async,
|
||||||
|
},
|
||||||
|
max_txpool_byte_size: 100_000_000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config values shared between the tx-pool and blockchain.
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct SharedStorageConfig {
|
||||||
|
/// The [`SyncMode`] of the database.
|
||||||
|
pub sync_mode: SyncMode,
|
||||||
|
}
|
42
binaries/cuprated/src/config/tracing_config.rs
Normal file
42
binaries/cuprated/src/config/tracing_config.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
|
||||||
|
/// [`tracing`] config.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct TracingConfig {
|
||||||
|
/// The default minimum log level.
|
||||||
|
#[serde(with = "level_filter_serde")]
|
||||||
|
level: LevelFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TracingConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
level: LevelFilter::INFO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod level_filter_serde {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
|
||||||
|
#[expect(clippy::trivially_copy_pass_by_ref, reason = "serde")]
|
||||||
|
pub fn serialize<S>(level_filter: &LevelFilter, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_str(&level_filter.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(d: D) -> Result<LevelFilter, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(d)?;
|
||||||
|
LevelFilter::from_str(&s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,9 +18,12 @@ pub const VERSION_BUILD: &str = if cfg!(debug_assertions) {
|
||||||
pub const PANIC_CRITICAL_SERVICE_ERROR: &str =
|
pub const PANIC_CRITICAL_SERVICE_ERROR: &str =
|
||||||
"A service critical to Cuprate's function returned an unexpected error.";
|
"A service critical to Cuprate's function returned an unexpected error.";
|
||||||
|
|
||||||
|
pub const EXAMPLE_CONFIG: &str = include_str!("../Cuprated.toml");
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn version() {
|
fn version() {
|
||||||
|
@ -35,4 +38,9 @@ mod test {
|
||||||
assert_eq!(VERSION_BUILD, "0.0.1-release");
|
assert_eq!(VERSION_BUILD, "0.0.1-release");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_config_text_is_valid() {
|
||||||
|
let config: Config = toml::from_str(EXAMPLE_CONFIG).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ fn main() {
|
||||||
// Initialize global static `LazyLock` data.
|
// Initialize global static `LazyLock` data.
|
||||||
statics::init_lazylock_statics();
|
statics::init_lazylock_statics();
|
||||||
|
|
||||||
|
let _config = config::read_config_and_args();
|
||||||
|
|
||||||
// TODO: everything else.
|
// TODO: everything else.
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,57 @@
|
||||||
//! P2P
|
//! P2P
|
||||||
//!
|
//!
|
||||||
//! Will handle initiating the P2P and contains a protocol request handler.
|
//! Will handle initiating the P2P and contains a protocol request handler.
|
||||||
|
use futures::{FutureExt, TryFutureExt};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
use cuprate_blockchain::service::BlockchainReadHandle;
|
||||||
|
use cuprate_consensus::BlockChainContextService;
|
||||||
|
use cuprate_p2p::{NetworkInterface, P2PConfig};
|
||||||
|
use cuprate_p2p_core::ClearNet;
|
||||||
|
use cuprate_txpool::service::TxpoolReadHandle;
|
||||||
|
|
||||||
|
use crate::txpool::IncomingTxHandler;
|
||||||
|
|
||||||
|
mod core_sync_service;
|
||||||
mod network_address;
|
mod network_address;
|
||||||
pub mod request_handler;
|
pub mod request_handler;
|
||||||
|
|
||||||
pub use network_address::CrossNetworkInternalPeerId;
|
pub use network_address::CrossNetworkInternalPeerId;
|
||||||
|
|
||||||
|
/// Starts the P2P clearnet network, returning a [`NetworkInterface`] to interact with it.
|
||||||
|
///
|
||||||
|
/// A [`oneshot::Sender`] is also returned to provide the [`IncomingTxHandler`], until this is provided network
|
||||||
|
/// handshakes can not be completed.
|
||||||
|
pub async fn start_clearnet_p2p(
|
||||||
|
blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
blockchain_context_service: BlockChainContextService,
|
||||||
|
txpool_read_handle: TxpoolReadHandle,
|
||||||
|
config: P2PConfig<ClearNet>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
NetworkInterface<ClearNet>,
|
||||||
|
oneshot::Sender<IncomingTxHandler>,
|
||||||
|
),
|
||||||
|
tower::BoxError,
|
||||||
|
> {
|
||||||
|
let (incoming_tx_handler_tx, incoming_tx_handler_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let request_handler_maker = request_handler::P2pProtocolRequestHandlerMaker {
|
||||||
|
blockchain_read_handle,
|
||||||
|
blockchain_context_service: blockchain_context_service.clone(),
|
||||||
|
txpool_read_handle,
|
||||||
|
incoming_tx_handler: None,
|
||||||
|
incoming_tx_handler_fut: incoming_tx_handler_rx.shared(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
cuprate_p2p::initialize_network(
|
||||||
|
request_handler_maker.map_response(|s| s.map_err(Into::into)),
|
||||||
|
core_sync_service::CoreSyncService(blockchain_context_service),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
incoming_tx_handler_tx,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
49
binaries/cuprated/src/p2p/core_sync_service.rs
Normal file
49
binaries/cuprated/src/p2p/core_sync_service.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use futures::{future::BoxFuture, FutureExt, TryFutureExt};
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use cuprate_consensus::{
|
||||||
|
BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService,
|
||||||
|
};
|
||||||
|
use cuprate_helper::{cast::usize_to_u64, map::split_u128_into_low_high_bits};
|
||||||
|
use cuprate_p2p_core::services::{CoreSyncDataRequest, CoreSyncDataResponse};
|
||||||
|
use cuprate_wire::CoreSyncData;
|
||||||
|
|
||||||
|
/// The core sync service.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CoreSyncService(pub BlockChainContextService);
|
||||||
|
|
||||||
|
impl Service<CoreSyncDataRequest> for CoreSyncService {
|
||||||
|
type Response = CoreSyncDataResponse;
|
||||||
|
type Error = tower::BoxError;
|
||||||
|
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.0.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future {
|
||||||
|
self.0
|
||||||
|
.call(BlockChainContextRequest::Context)
|
||||||
|
.map_ok(|res| {
|
||||||
|
let BlockChainContextResponse::Context(context) = res else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = context.unchecked_blockchain_context();
|
||||||
|
let (cumulative_difficulty, cumulative_difficulty_top64) =
|
||||||
|
split_u128_into_low_high_bits(context.cumulative_difficulty);
|
||||||
|
|
||||||
|
CoreSyncDataResponse(CoreSyncData {
|
||||||
|
cumulative_difficulty,
|
||||||
|
cumulative_difficulty_top64,
|
||||||
|
current_height: usize_to_u64(context.chain_height),
|
||||||
|
pruning_seed: 0,
|
||||||
|
top_id: context.top_hash,
|
||||||
|
top_version: context.current_hf.as_u8(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,422 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
future::{ready, Ready},
|
||||||
|
hash::Hash,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::{
|
||||||
|
future::{BoxFuture, Shared},
|
||||||
|
FutureExt,
|
||||||
|
};
|
||||||
|
use monero_serai::{block::Block, transaction::Transaction};
|
||||||
|
use tokio::sync::{broadcast, oneshot, watch};
|
||||||
|
use tokio_stream::wrappers::WatchStream;
|
||||||
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
|
use cuprate_blockchain::service::BlockchainReadHandle;
|
||||||
|
use cuprate_consensus::{
|
||||||
|
transactions::new_tx_verification_data, BlockChainContextRequest, BlockChainContextResponse,
|
||||||
|
BlockChainContextService,
|
||||||
|
};
|
||||||
|
use cuprate_dandelion_tower::TxState;
|
||||||
|
use cuprate_fixed_bytes::ByteArrayVec;
|
||||||
|
use cuprate_helper::cast::u64_to_usize;
|
||||||
|
use cuprate_helper::{
|
||||||
|
asynch::rayon_spawn_async,
|
||||||
|
cast::usize_to_u64,
|
||||||
|
map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits},
|
||||||
|
};
|
||||||
|
use cuprate_p2p::constants::{
|
||||||
|
MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MAX_BLOCK_BATCH_LEN, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN,
|
||||||
|
};
|
||||||
|
use cuprate_p2p_core::{
|
||||||
|
client::{InternalPeerID, PeerInformation},
|
||||||
|
NetZoneAddress, NetworkZone, ProtocolRequest, ProtocolResponse,
|
||||||
|
};
|
||||||
|
use cuprate_txpool::service::TxpoolReadHandle;
|
||||||
|
use cuprate_types::{
|
||||||
|
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||||
|
BlockCompleteEntry, TransactionBlobs, TxsInBlock,
|
||||||
|
};
|
||||||
|
use cuprate_wire::protocol::{
|
||||||
|
ChainRequest, ChainResponse, FluffyMissingTransactionsRequest, GetObjectsRequest,
|
||||||
|
GetObjectsResponse, NewFluffyBlock, NewTransactions,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
blockchain::interface::{self as blockchain_interface, IncomingBlockError},
|
||||||
|
constants::PANIC_CRITICAL_SERVICE_ERROR,
|
||||||
|
p2p::CrossNetworkInternalPeerId,
|
||||||
|
txpool::{IncomingTxError, IncomingTxHandler, IncomingTxs},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The P2P protocol request handler [`MakeService`](tower::MakeService).
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct P2pProtocolRequestHandlerMaker {
|
||||||
|
pub blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
pub blockchain_context_service: BlockChainContextService,
|
||||||
|
pub txpool_read_handle: TxpoolReadHandle,
|
||||||
|
|
||||||
|
/// The [`IncomingTxHandler`], wrapped in an [`Option`] as there is a cyclic reference between [`P2pProtocolRequestHandlerMaker`]
|
||||||
|
/// and the [`IncomingTxHandler`].
|
||||||
|
pub incoming_tx_handler: Option<IncomingTxHandler>,
|
||||||
|
|
||||||
|
/// A [`Future`](std::future::Future) that produces the [`IncomingTxHandler`].
|
||||||
|
pub incoming_tx_handler_fut: Shared<oneshot::Receiver<IncomingTxHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: NetZoneAddress> Service<PeerInformation<A>> for P2pProtocolRequestHandlerMaker
|
||||||
|
where
|
||||||
|
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
|
||||||
|
{
|
||||||
|
type Response = P2pProtocolRequestHandler<A>;
|
||||||
|
type Error = tower::BoxError;
|
||||||
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
if self.incoming_tx_handler.is_none() {
|
||||||
|
return self
|
||||||
|
.incoming_tx_handler_fut
|
||||||
|
.poll_unpin(cx)
|
||||||
|
.map(|incoming_tx_handler| {
|
||||||
|
self.incoming_tx_handler = Some(incoming_tx_handler?);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, peer_information: PeerInformation<A>) -> Self::Future {
|
||||||
|
let Some(incoming_tx_handler) = self.incoming_tx_handler.clone() else {
|
||||||
|
panic!("poll_ready was not called or did not return `Poll::Ready`")
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: check sync info?
|
||||||
|
|
||||||
|
let blockchain_read_handle = self.blockchain_read_handle.clone();
|
||||||
|
let txpool_read_handle = self.txpool_read_handle.clone();
|
||||||
|
|
||||||
|
ready(Ok(P2pProtocolRequestHandler {
|
||||||
|
peer_information,
|
||||||
|
blockchain_read_handle,
|
||||||
|
blockchain_context_service: self.blockchain_context_service.clone(),
|
||||||
|
txpool_read_handle,
|
||||||
|
incoming_tx_handler,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The P2P protocol request handler.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct P2pProtocolRequestHandler<N: NetZoneAddress> {
|
||||||
|
peer_information: PeerInformation<N>,
|
||||||
|
blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
blockchain_context_service: BlockChainContextService,
|
||||||
|
txpool_read_handle: TxpoolReadHandle,
|
||||||
|
incoming_tx_handler: IncomingTxHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: NetZoneAddress> Service<ProtocolRequest> for P2pProtocolRequestHandler<A>
|
||||||
|
where
|
||||||
|
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
|
||||||
|
{
|
||||||
|
type Response = ProtocolResponse;
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, request: ProtocolRequest) -> Self::Future {
|
||||||
|
match request {
|
||||||
|
ProtocolRequest::GetObjects(r) => {
|
||||||
|
get_objects(r, self.blockchain_read_handle.clone()).boxed()
|
||||||
|
}
|
||||||
|
ProtocolRequest::GetChain(r) => {
|
||||||
|
get_chain(r, self.blockchain_read_handle.clone()).boxed()
|
||||||
|
}
|
||||||
|
ProtocolRequest::FluffyMissingTxs(r) => {
|
||||||
|
fluffy_missing_txs(r, self.blockchain_read_handle.clone()).boxed()
|
||||||
|
}
|
||||||
|
ProtocolRequest::NewBlock(_) => ready(Err(anyhow::anyhow!(
|
||||||
|
"Peer sent a full block when we support fluffy blocks"
|
||||||
|
)))
|
||||||
|
.boxed(),
|
||||||
|
ProtocolRequest::NewFluffyBlock(r) => new_fluffy_block(
|
||||||
|
self.peer_information.clone(),
|
||||||
|
r,
|
||||||
|
self.blockchain_read_handle.clone(),
|
||||||
|
self.txpool_read_handle.clone(),
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
ProtocolRequest::NewTransactions(r) => new_transactions(
|
||||||
|
self.peer_information.clone(),
|
||||||
|
r,
|
||||||
|
self.blockchain_context_service.clone(),
|
||||||
|
self.incoming_tx_handler.clone(),
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
ProtocolRequest::GetTxPoolCompliment(_) => ready(Ok(ProtocolResponse::NA)).boxed(), // TODO: should we support this?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- Handler functions
|
||||||
|
|
||||||
|
/// [`ProtocolRequest::GetObjects`]
|
||||||
|
async fn get_objects(
|
||||||
|
request: GetObjectsRequest,
|
||||||
|
mut blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
) -> anyhow::Result<ProtocolResponse> {
|
||||||
|
if request.blocks.len() > MAX_BLOCK_BATCH_LEN {
|
||||||
|
anyhow::bail!("Peer requested more blocks than allowed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_hashes: Vec<[u8; 32]> = (&request.blocks).into();
|
||||||
|
// deallocate the backing `Bytes`.
|
||||||
|
drop(request);
|
||||||
|
|
||||||
|
let BlockchainResponse::BlockCompleteEntries {
|
||||||
|
blocks,
|
||||||
|
missing_hashes,
|
||||||
|
blockchain_height,
|
||||||
|
} = blockchain_read_handle
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(BlockchainReadRequest::BlockCompleteEntries(block_hashes))
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ProtocolResponse::GetObjects(GetObjectsResponse {
|
||||||
|
blocks,
|
||||||
|
missed_ids: ByteArrayVec::from(missing_hashes),
|
||||||
|
current_blockchain_height: usize_to_u64(blockchain_height),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`ProtocolRequest::GetChain`]
|
||||||
|
async fn get_chain(
|
||||||
|
request: ChainRequest,
|
||||||
|
mut blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
) -> anyhow::Result<ProtocolResponse> {
|
||||||
|
if request.block_ids.len() > MAX_BLOCKS_IDS_IN_CHAIN_ENTRY {
|
||||||
|
anyhow::bail!("Peer sent too many block hashes in chain request.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_hashes: Vec<[u8; 32]> = (&request.block_ids).into();
|
||||||
|
let want_pruned_data = request.prune;
|
||||||
|
// deallocate the backing `Bytes`.
|
||||||
|
drop(request);
|
||||||
|
|
||||||
|
let BlockchainResponse::NextChainEntry {
|
||||||
|
start_height,
|
||||||
|
chain_height,
|
||||||
|
block_ids,
|
||||||
|
block_weights,
|
||||||
|
cumulative_difficulty,
|
||||||
|
first_block_blob,
|
||||||
|
} = blockchain_read_handle
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(BlockchainReadRequest::NextChainEntry(block_hashes, 10_000))
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(start_height) = start_height else {
|
||||||
|
anyhow::bail!("The peers chain has a different genesis block than ours.");
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cumulative_difficulty_low64, cumulative_difficulty_top64) =
|
||||||
|
split_u128_into_low_high_bits(cumulative_difficulty);
|
||||||
|
|
||||||
|
Ok(ProtocolResponse::GetChain(ChainResponse {
|
||||||
|
start_height: usize_to_u64(std::num::NonZero::get(start_height)),
|
||||||
|
total_height: usize_to_u64(chain_height),
|
||||||
|
cumulative_difficulty_low64,
|
||||||
|
cumulative_difficulty_top64,
|
||||||
|
m_block_ids: ByteArrayVec::from(block_ids),
|
||||||
|
first_block: first_block_blob.map_or(Bytes::new(), Bytes::from),
|
||||||
|
// only needed when pruned
|
||||||
|
m_block_weights: if want_pruned_data {
|
||||||
|
block_weights.into_iter().map(usize_to_u64).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`ProtocolRequest::FluffyMissingTxs`]
|
||||||
|
async fn fluffy_missing_txs(
|
||||||
|
mut request: FluffyMissingTransactionsRequest,
|
||||||
|
mut blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
) -> anyhow::Result<ProtocolResponse> {
|
||||||
|
let tx_indexes = std::mem::take(&mut request.missing_tx_indices);
|
||||||
|
let block_hash: [u8; 32] = *request.block_hash;
|
||||||
|
let current_blockchain_height = request.current_blockchain_height;
|
||||||
|
|
||||||
|
// deallocate the backing `Bytes`.
|
||||||
|
drop(request);
|
||||||
|
|
||||||
|
let BlockchainResponse::TxsInBlock(res) = blockchain_read_handle
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(BlockchainReadRequest::TxsInBlock {
|
||||||
|
block_hash,
|
||||||
|
tx_indexes,
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(TxsInBlock { block, txs }) = res else {
|
||||||
|
anyhow::bail!("The peer requested txs out of range.");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ProtocolResponse::NewFluffyBlock(NewFluffyBlock {
|
||||||
|
b: BlockCompleteEntry {
|
||||||
|
block: Bytes::from(block),
|
||||||
|
txs: TransactionBlobs::Normal(txs.into_iter().map(Bytes::from).collect()),
|
||||||
|
pruned: false,
|
||||||
|
// only needed for pruned blocks.
|
||||||
|
block_weight: 0,
|
||||||
|
},
|
||||||
|
current_blockchain_height,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`ProtocolRequest::NewFluffyBlock`]
|
||||||
|
async fn new_fluffy_block<A: NetZoneAddress>(
|
||||||
|
peer_information: PeerInformation<A>,
|
||||||
|
request: NewFluffyBlock,
|
||||||
|
mut blockchain_read_handle: BlockchainReadHandle,
|
||||||
|
mut txpool_read_handle: TxpoolReadHandle,
|
||||||
|
) -> anyhow::Result<ProtocolResponse> {
|
||||||
|
// TODO: check context service here and ignore the block?
|
||||||
|
let current_blockchain_height = request.current_blockchain_height;
|
||||||
|
|
||||||
|
peer_information
|
||||||
|
.core_sync_data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.current_height = current_blockchain_height;
|
||||||
|
|
||||||
|
let (block, txs) = rayon_spawn_async(move || -> Result<_, anyhow::Error> {
|
||||||
|
let block = Block::read(&mut request.b.block.as_ref())?;
|
||||||
|
|
||||||
|
let tx_blobs = request
|
||||||
|
.b
|
||||||
|
.txs
|
||||||
|
.take_normal()
|
||||||
|
.ok_or(anyhow::anyhow!("Peer sent pruned txs in fluffy block"))?;
|
||||||
|
|
||||||
|
let txs = tx_blobs
|
||||||
|
.into_iter()
|
||||||
|
.map(|tx_blob| {
|
||||||
|
if tx_blob.len() > MAX_TRANSACTION_BLOB_SIZE {
|
||||||
|
anyhow::bail!("Peer sent a transaction over the size limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = Transaction::read(&mut tx_blob.as_ref())?;
|
||||||
|
|
||||||
|
Ok((tx.hash(), tx))
|
||||||
|
})
|
||||||
|
.collect::<Result<_, anyhow::Error>>()?;
|
||||||
|
|
||||||
|
// The backing `Bytes` will be deallocated when this closure returns.
|
||||||
|
|
||||||
|
Ok((block, txs))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = blockchain_interface::handle_incoming_block(
|
||||||
|
block,
|
||||||
|
txs,
|
||||||
|
&mut blockchain_read_handle,
|
||||||
|
&mut txpool_read_handle,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => Ok(ProtocolResponse::NA),
|
||||||
|
Err(IncomingBlockError::UnknownTransactions(block_hash, missing_tx_indices)) => Ok(
|
||||||
|
ProtocolResponse::FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest {
|
||||||
|
block_hash: block_hash.into(),
|
||||||
|
current_blockchain_height,
|
||||||
|
missing_tx_indices: missing_tx_indices.into_iter().map(usize_to_u64).collect(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Err(IncomingBlockError::Orphan) => {
|
||||||
|
// Block's parent was unknown, could be syncing?
|
||||||
|
Ok(ProtocolResponse::NA)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`ProtocolRequest::NewTransactions`]
|
||||||
|
async fn new_transactions<A>(
|
||||||
|
peer_information: PeerInformation<A>,
|
||||||
|
request: NewTransactions,
|
||||||
|
mut blockchain_context_service: BlockChainContextService,
|
||||||
|
mut incoming_tx_handler: IncomingTxHandler,
|
||||||
|
) -> anyhow::Result<ProtocolResponse>
|
||||||
|
where
|
||||||
|
A: NetZoneAddress,
|
||||||
|
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
|
||||||
|
{
|
||||||
|
let BlockChainContextResponse::Context(context) = blockchain_context_service
|
||||||
|
.ready()
|
||||||
|
.await
|
||||||
|
.expect(PANIC_CRITICAL_SERVICE_ERROR)
|
||||||
|
.call(BlockChainContextRequest::Context)
|
||||||
|
.await
|
||||||
|
.expect(PANIC_CRITICAL_SERVICE_ERROR)
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = context.unchecked_blockchain_context();
|
||||||
|
|
||||||
|
// If we are more than 2 blocks behind the peer then ignore the txs - we are probably still syncing.
|
||||||
|
if usize_to_u64(context.chain_height + 2)
|
||||||
|
< peer_information
|
||||||
|
.core_sync_data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.current_height
|
||||||
|
{
|
||||||
|
return Ok(ProtocolResponse::NA);
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = if request.dandelionpp_fluff {
|
||||||
|
TxState::Fluff
|
||||||
|
} else {
|
||||||
|
TxState::Stem {
|
||||||
|
from: peer_information.id.into(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drop all the data except the stuff we still need.
|
||||||
|
let NewTransactions { txs, .. } = request;
|
||||||
|
|
||||||
|
let res = incoming_tx_handler
|
||||||
|
.ready()
|
||||||
|
.await
|
||||||
|
.expect(PANIC_CRITICAL_SERVICE_ERROR)
|
||||||
|
.call(IncomingTxs { txs, state })
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(()) => Ok(ProtocolResponse::NA),
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
||||||
macro_rules! define_init_lazylock_statics {
|
macro_rules! define_init_lazylock_statics {
|
||||||
($(
|
($(
|
||||||
$( #[$attr:meta] )*
|
$( #[$attr:meta] )*
|
||||||
$name:ident: $t:ty = $init_fn:expr;
|
$name:ident: $t:ty = $init_fn:expr_2021;
|
||||||
)*) => {
|
)*) => {
|
||||||
/// Initialize global static `LazyLock` data.
|
/// Initialize global static `LazyLock` data.
|
||||||
pub fn init_lazylock_statics() {
|
pub fn init_lazylock_statics() {
|
||||||
|
|
|
@ -12,4 +12,4 @@ mod dandelion;
|
||||||
mod incoming_tx;
|
mod incoming_tx;
|
||||||
mod txs_being_handled;
|
mod txs_being_handled;
|
||||||
|
|
||||||
pub use incoming_tx::IncomingTxHandler;
|
pub use incoming_tx::{IncomingTxError, IncomingTxHandler, IncomingTxs};
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub fn dandelion_router(clear_net: NetworkInterface<ClearNet>) -> ConcreteDandel
|
||||||
diffuse_service::DiffuseService {
|
diffuse_service::DiffuseService {
|
||||||
clear_net_broadcast_service: clear_net.broadcast_svc(),
|
clear_net_broadcast_service: clear_net.broadcast_svc(),
|
||||||
},
|
},
|
||||||
stem_service::OutboundPeerStream { clear_net },
|
stem_service::OutboundPeerStream::new(clear_net),
|
||||||
DANDELION_CONFIG,
|
DANDELION_CONFIG,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{ready, Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::Stream;
|
use futures::{future::BoxFuture, FutureExt, Stream};
|
||||||
use tower::Service;
|
use tower::Service;
|
||||||
|
|
||||||
use cuprate_dandelion_tower::{traits::StemRequest, OutboundPeer};
|
use cuprate_dandelion_tower::{traits::StemRequest, OutboundPeer};
|
||||||
use cuprate_p2p::{ClientPoolDropGuard, NetworkInterface};
|
use cuprate_p2p::{ClientDropGuard, NetworkInterface, PeerSetRequest, PeerSetResponse};
|
||||||
use cuprate_p2p_core::{
|
use cuprate_p2p_core::{
|
||||||
client::{Client, InternalPeerID},
|
client::{Client, InternalPeerID},
|
||||||
ClearNet, NetworkZone, PeerRequest, ProtocolRequest,
|
ClearNet, NetworkZone, PeerRequest, ProtocolRequest,
|
||||||
|
@ -19,7 +20,17 @@ use crate::{p2p::CrossNetworkInternalPeerId, txpool::dandelion::DandelionTx};
|
||||||
|
|
||||||
/// The dandelion outbound peer stream.
|
/// The dandelion outbound peer stream.
|
||||||
pub struct OutboundPeerStream {
|
pub struct OutboundPeerStream {
|
||||||
pub clear_net: NetworkInterface<ClearNet>,
|
clear_net: NetworkInterface<ClearNet>,
|
||||||
|
state: OutboundPeerStreamState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutboundPeerStream {
|
||||||
|
pub const fn new(clear_net: NetworkInterface<ClearNet>) -> Self {
|
||||||
|
Self {
|
||||||
|
clear_net,
|
||||||
|
state: OutboundPeerStreamState::Standby,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for OutboundPeerStream {
|
impl Stream for OutboundPeerStream {
|
||||||
|
@ -28,23 +39,49 @@ impl Stream for OutboundPeerStream {
|
||||||
tower::BoxError,
|
tower::BoxError,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
// TODO: make the outbound peer choice random.
|
loop {
|
||||||
Poll::Ready(Some(Ok(self
|
match &mut self.state {
|
||||||
.clear_net
|
OutboundPeerStreamState::Standby => {
|
||||||
.client_pool()
|
let peer_set = self.clear_net.peer_set();
|
||||||
.outbound_client()
|
let res = ready!(peer_set.poll_ready(cx));
|
||||||
.map_or(OutboundPeer::Exhausted, |client| {
|
|
||||||
OutboundPeer::Peer(
|
self.state = OutboundPeerStreamState::AwaitingPeer(
|
||||||
CrossNetworkInternalPeerId::ClearNet(client.info.id),
|
peer_set.call(PeerSetRequest::StemPeer).boxed(),
|
||||||
StemPeerService(client),
|
);
|
||||||
)
|
}
|
||||||
}))))
|
OutboundPeerStreamState::AwaitingPeer(fut) => {
|
||||||
|
let res = ready!(fut.poll_unpin(cx));
|
||||||
|
|
||||||
|
return Poll::Ready(Some(res.map(|res| {
|
||||||
|
let PeerSetResponse::StemPeer(stem_peer) = res else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
match stem_peer {
|
||||||
|
Some(peer) => OutboundPeer::Peer(
|
||||||
|
CrossNetworkInternalPeerId::ClearNet(peer.info.id),
|
||||||
|
StemPeerService(peer),
|
||||||
|
),
|
||||||
|
None => OutboundPeer::Exhausted,
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state of the [`OutboundPeerStream`].
|
||||||
|
enum OutboundPeerStreamState {
|
||||||
|
/// Standby state.
|
||||||
|
Standby,
|
||||||
|
/// Awaiting a response from the peer-set.
|
||||||
|
AwaitingPeer(BoxFuture<'static, Result<PeerSetResponse<ClearNet>, tower::BoxError>>),
|
||||||
|
}
|
||||||
|
|
||||||
/// The stem service, used to send stem txs.
|
/// The stem service, used to send stem txs.
|
||||||
pub struct StemPeerService<N: NetworkZone>(ClientPoolDropGuard<N>);
|
pub struct StemPeerService<N: NetworkZone>(ClientDropGuard<N>);
|
||||||
|
|
||||||
impl<N: NetworkZone> Service<StemRequest<DandelionTx>> for StemPeerService<N> {
|
impl<N: NetworkZone> Service<StemRequest<DandelionTx>> for StemPeerService<N> {
|
||||||
type Response = <Client<N> as Service<PeerRequest>>::Response;
|
type Response = <Client<N> as Service<PeerRequest>>::Response;
|
||||||
|
|
|
@ -43,9 +43,13 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An error that can happen handling an incoming tx.
|
/// An error that can happen handling an incoming tx.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IncomingTxError {
|
pub enum IncomingTxError {
|
||||||
|
#[error("Error parsing tx: {0}")]
|
||||||
Parse(std::io::Error),
|
Parse(std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
Consensus(ExtendedConsensusError),
|
Consensus(ExtendedConsensusError),
|
||||||
|
#[error("Duplicate tx in message")]
|
||||||
DuplicateTransaction,
|
DuplicateTransaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +71,7 @@ pub(super) type TxId = [u8; 32];
|
||||||
/// The service than handles incoming transaction pool transactions.
|
/// The service than handles incoming transaction pool transactions.
|
||||||
///
|
///
|
||||||
/// This service handles everything including verifying the tx, adding it to the pool and routing it to other nodes.
|
/// This service handles everything including verifying the tx, adding it to the pool and routing it to other nodes.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct IncomingTxHandler {
|
pub struct IncomingTxHandler {
|
||||||
/// A store of txs currently being handled in incoming tx requests.
|
/// A store of txs currently being handled in incoming tx requests.
|
||||||
pub(super) txs_being_handled: TxsBeingHandled,
|
pub(super) txs_being_handled: TxsBeingHandled,
|
||||||
|
|
|
@ -143,9 +143,16 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [⚪️ Benchmarking](benchmarking/intro.md)
|
- [🟢 Benchmarking](benchmarking/intro.md)
|
||||||
- [⚪️ Criterion](benchmarking/criterion.md)
|
- [🟢 Criterion](benchmarking/criterion/intro.md)
|
||||||
- [⚪️ Harness](benchmarking/harness.md)
|
- [🟢 Creating](benchmarking/criterion/creating.md)
|
||||||
|
- [🟢 Running](benchmarking/criterion/running.md)
|
||||||
|
- [🟢 `cuprate-benchmark`](benchmarking/cuprate/intro.md)
|
||||||
|
- [🟢 Creating](benchmarking/cuprate/creating.md)
|
||||||
|
- [🟢 Running](benchmarking/cuprate/running.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
- [⚪️ Testing](testing/intro.md)
|
- [⚪️ Testing](testing/intro.md)
|
||||||
- [⚪️ Monero data](testing/monero-data.md)
|
- [⚪️ Monero data](testing/monero-data.md)
|
||||||
- [⚪️ RPC client](testing/rpc-client.md)
|
- [⚪️ RPC client](testing/rpc-client.md)
|
||||||
|
@ -157,6 +164,11 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- [🟢 Monero oddities](oddities/intro.md)
|
||||||
|
- [🟡 Little-endian IPv4 addresses](oddities/le-ipv4.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
- [⚪️ Appendix](appendix/intro.md)
|
- [⚪️ Appendix](appendix/intro.md)
|
||||||
- [🟢 Crates](appendix/crates.md)
|
- [🟢 Crates](appendix/crates.md)
|
||||||
- [🔴 Contributing](appendix/contributing.md)
|
- [🔴 Contributing](appendix/contributing.md)
|
||||||
|
|
|
@ -54,6 +54,11 @@ cargo doc --open --package cuprate-blockchain
|
||||||
| [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`rpc/interface/`](https://github.com/Cuprate/cuprate/tree/main/rpc/interface) | RPC interface & routing
|
| [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`rpc/interface/`](https://github.com/Cuprate/cuprate/tree/main/rpc/interface) | RPC interface & routing
|
||||||
| [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`rpc/handler/`](https://github.com/Cuprate/cuprate/tree/main/rpc/handler) | RPC inner handlers
|
| [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`rpc/handler/`](https://github.com/Cuprate/cuprate/tree/main/rpc/handler) | RPC inner handlers
|
||||||
|
|
||||||
|
## ZMQ
|
||||||
|
| Crate | In-tree path | Purpose |
|
||||||
|
|-------|--------------|---------|
|
||||||
|
| [`cuprate-zmq-types`](https://doc.cuprate.org/cuprate_zmq_types) | [`zmq/types/`](https://github.com/Cuprate/cuprate/tree/main/zmq/types) | Message types for ZMQ Pub/Sub interface
|
||||||
|
|
||||||
## 1-off crates
|
## 1-off crates
|
||||||
| Crate | In-tree path | Purpose |
|
| Crate | In-tree path | Purpose |
|
||||||
|-------|--------------|---------|
|
|-------|--------------|---------|
|
||||||
|
@ -63,3 +68,11 @@ cargo doc --open --package cuprate-blockchain
|
||||||
| [`cuprate-helper`](https://doc.cuprate.org/cuprate_helper) | [`helper/`](https://github.com/Cuprate/cuprate/tree/main/helper) | Kitchen-sink helper crate for Cuprate
|
| [`cuprate-helper`](https://doc.cuprate.org/cuprate_helper) | [`helper/`](https://github.com/Cuprate/cuprate/tree/main/helper) | Kitchen-sink helper crate for Cuprate
|
||||||
| [`cuprate-test-utils`](https://doc.cuprate.org/cuprate_test_utils) | [`test-utils/`](https://github.com/Cuprate/cuprate/tree/main/test-utils) | Testing utilities for Cuprate
|
| [`cuprate-test-utils`](https://doc.cuprate.org/cuprate_test_utils) | [`test-utils/`](https://github.com/Cuprate/cuprate/tree/main/test-utils) | Testing utilities for Cuprate
|
||||||
| [`cuprate-types`](https://doc.cuprate.org/cuprate_types) | [`types/`](https://github.com/Cuprate/cuprate/tree/main/types) | Shared types across Cuprate
|
| [`cuprate-types`](https://doc.cuprate.org/cuprate_types) | [`types/`](https://github.com/Cuprate/cuprate/tree/main/types) | Shared types across Cuprate
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
| Crate | In-tree path | Purpose |
|
||||||
|
|-------|--------------|---------|
|
||||||
|
| [`cuprate-benchmark`](https://doc.cuprate.org/cuprate_benchmark) | [`benches/benchmark/bin/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | Cuprate benchmarking binary
|
||||||
|
| [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) | [`benches/benchmark/lib/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | Cuprate benchmarking library
|
||||||
|
| `cuprate-benchmark-*` | [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/) | Benchmark for a Cuprate crate that uses `cuprate-benchmark`
|
||||||
|
| `cuprate-criterion-*` | [`benches/criterion/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Benchmark for a Cuprate crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book)
|
|
@ -1 +0,0 @@
|
||||||
# ⚪️ Criterion
|
|
21
books/architecture/src/benchmarking/criterion/creating.md
Normal file
21
books/architecture/src/benchmarking/criterion/creating.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Creating
|
||||||
|
Creating a new Criterion-based benchmarking crate for one of Cuprate's crates is relatively simple,
|
||||||
|
although, it requires knowledge of how to use Criterion first:
|
||||||
|
|
||||||
|
1. Read the `Getting Started` section of <https://bheisler.github.io/criterion.rs/book>
|
||||||
|
2. Copy [`benches/criterion/example`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example) as base
|
||||||
|
3. Get started
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
New benchmark crates using Criterion should:
|
||||||
|
- Be in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/)
|
||||||
|
- Be in the `cuprate-criterion-$CRATE_NAME` format
|
||||||
|
|
||||||
|
For a real example, see:
|
||||||
|
[`cuprate-criterion-json-rpc`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc).
|
||||||
|
|
||||||
|
## Workspace
|
||||||
|
Finally, make sure to add the benchmark crate to the workspace
|
||||||
|
[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file.
|
||||||
|
|
||||||
|
Your benchmark is now ready to be ran.
|
4
books/architecture/src/benchmarking/criterion/intro.md
Normal file
4
books/architecture/src/benchmarking/criterion/intro.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Criterion
|
||||||
|
Each sub-directory in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) is a crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book) for timing single functions and/or groups of functions.
|
||||||
|
|
||||||
|
They are generally be small in scope.
|
15
books/architecture/src/benchmarking/criterion/running.md
Normal file
15
books/architecture/src/benchmarking/criterion/running.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Running
|
||||||
|
To run all Criterion benchmarks, run this from the repository root:
|
||||||
|
```bash
|
||||||
|
cargo bench
|
||||||
|
```
|
||||||
|
|
||||||
|
To run specific package(s), use:
|
||||||
|
```bash
|
||||||
|
cargo bench --package $CRITERION_BENCHMARK_CRATE_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```bash
|
||||||
|
cargo bench --package cuprate-criterion-json-rpc
|
||||||
|
```
|
57
books/architecture/src/benchmarking/cuprate/creating.md
Normal file
57
books/architecture/src/benchmarking/cuprate/creating.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# Creating
|
||||||
|
New benchmarks are plugged into `cuprate-benchmark` by:
|
||||||
|
1. Implementing `cuprate_benchmark_lib::Benchmark`
|
||||||
|
1. Registering the benchmark in the `cuprate_benchmark` binary
|
||||||
|
|
||||||
|
See [`benches/benchmark/example`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example)
|
||||||
|
for an example.
|
||||||
|
|
||||||
|
## Creating the benchmark crate
|
||||||
|
Before plugging into `cuprate-benchmark`, your actual benchmark crate must be created:
|
||||||
|
|
||||||
|
1. Create a new crate inside `benches/benchmark` (consider copying `benches/benchmark/example` as a base)
|
||||||
|
1. Pull in `cuprate_benchmark_lib` as a dependency
|
||||||
|
1. Create a benchmark
|
||||||
|
1. Implement `cuprate_benchmark_lib::Benchmark`
|
||||||
|
|
||||||
|
New benchmark crates using `cuprate-database` should:
|
||||||
|
- Be in [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/)
|
||||||
|
- Be in the `cuprate-benchmark-$CRATE_NAME` format
|
||||||
|
|
||||||
|
For a real example, see:
|
||||||
|
[`cuprate-benchmark-database`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/cuprate-database).
|
||||||
|
|
||||||
|
## `cuprate_benchmark_lib::Benchmark`
|
||||||
|
This is the trait that standardizes all benchmarks ran under `cuprate-benchmark`.
|
||||||
|
|
||||||
|
It must be implemented by your benchmarking crate.
|
||||||
|
|
||||||
|
See `cuprate-benchmark-lib` crate documentation for a user-guide: <https://doc.cuprate.org/cuprate_benchmark_lib>.
|
||||||
|
|
||||||
|
## Adding a feature to `cuprate-benchmark`
|
||||||
|
After your benchmark's behavior is defined, it must be registered
|
||||||
|
in the binary that is actually ran: `cuprate-benchmark`.
|
||||||
|
|
||||||
|
If your benchmark is new, add a new crate feature to [`cuprate-benchmark`'s Cargo.toml file](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/Cargo.toml) with an optional dependency to your benchmarking crate.
|
||||||
|
|
||||||
|
Please remember to edit the feature table in the
|
||||||
|
[`README.md`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/README.md) as well!
|
||||||
|
|
||||||
|
## Adding to `cuprate-benchmark`'s `main()`
|
||||||
|
After adding your crate's feature, add a conditional line that run the benchmark
|
||||||
|
if the feature is enabled to the `main()` function:
|
||||||
|
|
||||||
|
For example, if your crate's name is `egg`:
|
||||||
|
```rust
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "egg")] {
|
||||||
|
run::run_benchmark::<cuprate_benchmark_egg::Benchmark>(&mut timings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace
|
||||||
|
Finally, make sure to add the benchmark crate to the workspace
|
||||||
|
[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file.
|
||||||
|
|
||||||
|
Your benchmark is now ready to be ran.
|
37
books/architecture/src/benchmarking/cuprate/intro.md
Normal file
37
books/architecture/src/benchmarking/cuprate/intro.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# cuprate-benchmark
|
||||||
|
Cuprate has 2 custom crates for general benchmarking:
|
||||||
|
- `cuprate-benchmark`; the actual binary crate ran
|
||||||
|
- `cuprate-benchmark-lib`; the library that other crates hook into
|
||||||
|
|
||||||
|
The abstract purpose of `cuprate-benchmark` is very simple:
|
||||||
|
1. Set-up the benchmark
|
||||||
|
1. Start timer
|
||||||
|
1. Run benchmark
|
||||||
|
1. Output data
|
||||||
|
|
||||||
|
`cuprate-benchmark` runs the benchmarks found in [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark).
|
||||||
|
|
||||||
|
`cuprate-benchmark-lib` defines the `Benchmark` trait that all
|
||||||
|
benchmark crates implement to "plug-in" to the benchmarking harness.
|
||||||
|
|
||||||
|
## Diagram
|
||||||
|
A diagram displaying the relation between `cuprate-benchmark` and related crates.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ cuprate_benchmark │
|
||||||
|
│ (actual binary ran) │
|
||||||
|
└──────────┬──────────┘
|
||||||
|
┌──────────────────┴───────────────────┐
|
||||||
|
│ cuprate_benchmark_lib │
|
||||||
|
│ ┌───────────────────────────────────┐│
|
||||||
|
│ │ trait Benchmark ││
|
||||||
|
│ └───────────────────────────────────┘│
|
||||||
|
└──────────────────┬───────────────────┘
|
||||||
|
┌───────────────────────────┐ │ ┌───────────────────────────┐
|
||||||
|
│ cuprate_benchmark_example ├──┼───┤ cuprate_benchmark_* │
|
||||||
|
└───────────────────────────┘ │ └───────────────────────────┘
|
||||||
|
┌───────────────────────────┐ │ ┌───────────────────────────┐
|
||||||
|
│ cuprate_benchmark_* ├──┴───┤ cuprate_benchmark_* │
|
||||||
|
└───────────────────────────┘ └───────────────────────────┘
|
||||||
|
```
|
16
books/architecture/src/benchmarking/cuprate/running.md
Normal file
16
books/architecture/src/benchmarking/cuprate/running.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Running
|
||||||
|
`cuprate-benchmark` benchmarks are ran with this command:
|
||||||
|
```bash
|
||||||
|
cargo run --release --package cuprate-benchmark --features $BENCHMARK_CRATE_FEATURE
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, to run the example benchmark:
|
||||||
|
```bash
|
||||||
|
cargo run --release --package cuprate-benchmark --features example
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the `all` feature to run all benchmarks:
|
||||||
|
```bash
|
||||||
|
# Run all benchmarks
|
||||||
|
cargo run --release --package cuprate-benchmark --features all
|
||||||
|
```
|
|
@ -1 +0,0 @@
|
||||||
# ⚪️ Harness
|
|
|
@ -1 +1,22 @@
|
||||||
# ⚪️ Benchmarking
|
# Benchmarking
|
||||||
|
Cuprate has 2 types of benchmarks:
|
||||||
|
- [Criterion](https://bheisler.github.io/criterion.rs/book/user_guide/advanced_configuration.html) benchmarks
|
||||||
|
- `cuprate-benchmark` benchmarks
|
||||||
|
|
||||||
|
Criterion is used for micro benchmarks; they time single functions, groups of functions, and generally are small in scope.
|
||||||
|
|
||||||
|
`cuprate-benchmark` and [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) are custom in-house crates Cuprate uses for macro benchmarks; these test sub-systems, sections of a sub-system, or otherwise larger or more complicated code that isn't well-suited for micro benchmarks.
|
||||||
|
|
||||||
|
## File layout and purpose
|
||||||
|
All benchmarking related files are in the [`benches/`](https://github.com/Cuprate/cuprate/tree/main/benches) folder.
|
||||||
|
|
||||||
|
This directory is organized like such:
|
||||||
|
|
||||||
|
| Directory | Purpose |
|
||||||
|
|-------------------------------|---------|
|
||||||
|
| [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Criterion (micro) benchmarks
|
||||||
|
| `benches/criterion/cuprate-*` | Criterion benchmarks for the crate with the same name
|
||||||
|
| [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark) | Cuprate's custom benchmarking files
|
||||||
|
| [`benches/benchmark/bin`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | The `cuprate-benchmark` crate; the actual binary run that links all benchmarks
|
||||||
|
| [`benches/benchmark/lib`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | The `cuprate-benchmark-lib` crate; the benchmarking framework all benchmarks plug into
|
||||||
|
| `benches/benchmark/cuprate-*` | `cuprate-benchmark` benchmarks for the crate with the same name
|
||||||
|
|
37
books/architecture/src/oddities/intro.md
Normal file
37
books/architecture/src/oddities/intro.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Monero oddities
|
||||||
|
This section is a list of any peculiar, interesting,
|
||||||
|
or non-standard behavior that Monero has that is not
|
||||||
|
planned on being changed or deprecated.
|
||||||
|
|
||||||
|
This section exists to hold all the small yet noteworthy knowledge in one place,
|
||||||
|
instead of in any single contributor's mind.
|
||||||
|
|
||||||
|
These are usually behaviors stemming from implementation rather than protocol/cryptography.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
This is the markdown formatting for each entry in this section.
|
||||||
|
|
||||||
|
If applicable, consider using this formatting when adding to this section.
|
||||||
|
|
||||||
|
```md
|
||||||
|
# <concise_title_of_the_behavior>
|
||||||
|
|
||||||
|
## What
|
||||||
|
A detailed description of the behavior.
|
||||||
|
|
||||||
|
## Expected
|
||||||
|
The norm or standard behavior that is usually expected.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
The reasoning behind why this behavior exists and/or
|
||||||
|
any links to more detailed discussion on the behavior.
|
||||||
|
|
||||||
|
## Affects
|
||||||
|
A (potentially non-exhaustive) list of places that this behavior can/does affect.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
An example link or section of code where the behavior occurs.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
A link to original `monerod` code that defines the behavior.
|
||||||
|
```
|
24
books/architecture/src/oddities/le-ipv4.md
Normal file
24
books/architecture/src/oddities/le-ipv4.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Little-endian IPv4 addresses
|
||||||
|
|
||||||
|
## What
|
||||||
|
Monero encodes IPv4 addresses in [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order.
|
||||||
|
|
||||||
|
## Expected
|
||||||
|
In general, [networking-related protocols/code use _networking order_ (big-endian)](https://en.wikipedia.org/wiki/Endianness#Networking).
|
||||||
|
|
||||||
|
## Why
|
||||||
|
TODO
|
||||||
|
|
||||||
|
- <https://github.com/monero-project/monero/issues/3826>
|
||||||
|
- <https://github.com/monero-project/monero/pull/5544>
|
||||||
|
|
||||||
|
## Affects
|
||||||
|
Any representation and (de)serialization of IPv4 addresses must keep little
|
||||||
|
endian in-mind, e.g. the P2P wire format or `int` encoded IPv4 addresses in RPC.
|
||||||
|
|
||||||
|
For example, [the `ip` field in `set_bans`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#set_bans).
|
||||||
|
|
||||||
|
For Cuprate, this means Rust's [`Ipv4Addr::from_bits/from`](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#1182) cannot be used in these cases as [it assumes big-endian encoding](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#540).
|
||||||
|
|
||||||
|
## Source
|
||||||
|
- <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/contrib/epee/include/net/net_utils_base.h#L97>
|
|
@ -328,8 +328,8 @@ fn next_difficulty(
|
||||||
time_span = 1;
|
time_span = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do checked operations here and unwrap so we don't silently overflow?
|
// TODO: do `checked_mul` here and unwrap so we don't silently overflow?
|
||||||
(windowed_work * u128::from(hf.block_time().as_secs()) + time_span - 1) / time_span
|
(windowed_work * u128::from(hf.block_time().as_secs())).div_ceil(time_span)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the start and end of the window to calculate difficulty.
|
/// Get the start and end of the window to calculate difficulty.
|
||||||
|
|
|
@ -9,7 +9,7 @@ use clap::Parser;
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
use cuprate_blockchain::{
|
use cuprate_blockchain::{
|
||||||
config::ConfigBuilder, cuprate_database::RuntimeError, service::BlockchainReadHandle,
|
config::ConfigBuilder, cuprate_database::DbResult, service::BlockchainReadHandle,
|
||||||
};
|
};
|
||||||
use cuprate_types::{
|
use cuprate_types::{
|
||||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||||
|
@ -23,7 +23,7 @@ const BATCH_SIZE: usize = 512;
|
||||||
async fn read_batch(
|
async fn read_batch(
|
||||||
handle: &mut BlockchainReadHandle,
|
handle: &mut BlockchainReadHandle,
|
||||||
height_from: usize,
|
height_from: usize,
|
||||||
) -> Result<Vec<BlockId>, RuntimeError> {
|
) -> DbResult<Vec<BlockId>> {
|
||||||
let mut block_ids = Vec::<BlockId>::with_capacity(BATCH_SIZE);
|
let mut block_ids = Vec::<BlockId>::with_capacity(BATCH_SIZE);
|
||||||
|
|
||||||
for height in height_from..(height_from + BATCH_SIZE) {
|
for height in height_from..(height_from + BATCH_SIZE) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub(crate) fn subarray_copy<T: AsRef<[U]> + ?Sized, U: Copy, const LEN: usize>(
|
||||||
/// A mutable reference to a fixed-size subarray of type `[U; LEN]`.
|
/// A mutable reference to a fixed-size subarray of type `[U; LEN]`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if `start + LEN > array.as_ref().len()`.
|
/// Panics if `start + LEN > array.as_mut().len()`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn subarray_mut<T: AsMut<[U]> + ?Sized, U, const LEN: usize>(
|
pub(crate) fn subarray_mut<T: AsMut<[U]> + ?Sized, U, const LEN: usize>(
|
||||||
array: &mut T,
|
array: &mut T,
|
||||||
|
|
|
@ -35,6 +35,8 @@ futures = { workspace = true, optional = true, features = ["std"] }
|
||||||
monero-serai = { workspace = true, optional = true }
|
monero-serai = { workspace = true, optional = true }
|
||||||
rayon = { workspace = true, optional = true }
|
rayon = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||||
|
|
||||||
# This is kinda a stupid work around.
|
# This is kinda a stupid work around.
|
||||||
# [thread] needs to activate one of these libs (windows|libc)
|
# [thread] needs to activate one of these libs (windows|libc)
|
||||||
# although it depends on what target we're building for.
|
# although it depends on what target we're building for.
|
||||||
|
|
|
@ -28,7 +28,12 @@
|
||||||
//! - <https://docs.rs/dirs>
|
//! - <https://docs.rs/dirs>
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Use
|
//---------------------------------------------------------------------------------------------------- Use
|
||||||
use std::{path::PathBuf, sync::LazyLock};
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::network::Network;
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Const
|
//---------------------------------------------------------------------------------------------------- Const
|
||||||
/// Cuprate's main directory.
|
/// Cuprate's main directory.
|
||||||
|
@ -58,6 +63,9 @@ pub const CUPRATE_DIR: &str = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The default name of Cuprate's config file.
|
||||||
|
pub const DEFAULT_CONFIG_FILE_NAME: &str = "Cuprated.toml";
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Directories
|
//---------------------------------------------------------------------------------------------------- Directories
|
||||||
/// Create a `LazyLock` for common PATHs used by Cuprate.
|
/// Create a `LazyLock` for common PATHs used by Cuprate.
|
||||||
///
|
///
|
||||||
|
@ -150,32 +158,61 @@ impl_path_lazylock! {
|
||||||
CUPRATE_DATA_DIR,
|
CUPRATE_DATA_DIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"",
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
/// Cuprate's blockchain directory.
|
/// Joins the [`Network`] to the [`Path`].
|
||||||
///
|
///
|
||||||
/// This is the PATH used for any Cuprate blockchain files.
|
/// This will keep the path the same for [`Network::Mainnet`].
|
||||||
///
|
fn path_with_network(path: &Path, network: Network) -> PathBuf {
|
||||||
/// | OS | PATH |
|
match network {
|
||||||
/// |---------|----------------------------------------------------------------|
|
Network::Mainnet => path.to_path_buf(),
|
||||||
/// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\blockchain\` |
|
network => path.join(network.to_string()),
|
||||||
/// | macOS | `/Users/Alice/Library/Application Support/Cuprate/blockchain/` |
|
}
|
||||||
/// | Linux | `/home/alice/.local/share/cuprate/blockchain/` |
|
}
|
||||||
CUPRATE_BLOCKCHAIN_DIR,
|
|
||||||
data_dir,
|
|
||||||
"blockchain",
|
|
||||||
|
|
||||||
/// Cuprate's transaction pool directory.
|
/// Cuprate's blockchain directory.
|
||||||
///
|
///
|
||||||
/// This is the PATH used for any Cuprate txpool files.
|
/// This is the PATH used for any Cuprate blockchain files.
|
||||||
///
|
///
|
||||||
/// | OS | PATH |
|
/// ```rust
|
||||||
/// |---------|------------------------------------------------------------|
|
/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, blockchain_path}};
|
||||||
/// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\txpool\` |
|
///
|
||||||
/// | macOS | `/Users/Alice/Library/Application Support/Cuprate/txpool/` |
|
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("blockchain"));
|
||||||
/// | Linux | `/home/alice/.local/share/cuprate/txpool/` |
|
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("blockchain"));
|
||||||
CUPRATE_TXPOOL_DIR,
|
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("blockchain"));
|
||||||
data_dir,
|
/// ```
|
||||||
"txpool",
|
pub fn blockchain_path(data_dir: &Path, network: Network) -> PathBuf {
|
||||||
|
path_with_network(data_dir, network).join("blockchain")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cuprate's txpool directory.
|
||||||
|
///
|
||||||
|
/// This is the PATH used for any Cuprate txpool files.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, txpool_path}};
|
||||||
|
///
|
||||||
|
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("txpool"));
|
||||||
|
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("txpool"));
|
||||||
|
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("txpool"));
|
||||||
|
/// ```
|
||||||
|
pub fn txpool_path(data_dir: &Path, network: Network) -> PathBuf {
|
||||||
|
path_with_network(data_dir, network).join("txpool")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cuprate's address-book directory.
|
||||||
|
///
|
||||||
|
/// This is the PATH used for any Cuprate address-book files.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cuprate_helper::{network::Network, fs::{CUPRATE_CACHE_DIR, address_book_path}};
|
||||||
|
///
|
||||||
|
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Mainnet).as_path(), CUPRATE_CACHE_DIR.join("addressbook"));
|
||||||
|
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Stagenet).as_path(), CUPRATE_CACHE_DIR.join(Network::Stagenet.to_string()).join("addressbook"));
|
||||||
|
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Testnet).as_path(), CUPRATE_CACHE_DIR.join(Network::Testnet.to_string()).join("addressbook"));
|
||||||
|
/// ```
|
||||||
|
pub fn address_book_path(cache_dir: &Path, network: Network) -> PathBuf {
|
||||||
|
path_with_network(cache_dir, network).join("addressbook")
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Tests
|
//---------------------------------------------------------------------------------------------------- Tests
|
||||||
|
@ -197,29 +234,21 @@ mod test {
|
||||||
(&*CUPRATE_CACHE_DIR, ""),
|
(&*CUPRATE_CACHE_DIR, ""),
|
||||||
(&*CUPRATE_CONFIG_DIR, ""),
|
(&*CUPRATE_CONFIG_DIR, ""),
|
||||||
(&*CUPRATE_DATA_DIR, ""),
|
(&*CUPRATE_DATA_DIR, ""),
|
||||||
(&*CUPRATE_BLOCKCHAIN_DIR, ""),
|
|
||||||
(&*CUPRATE_TXPOOL_DIR, ""),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
array[0].1 = r"AppData\Local\Cuprate";
|
array[0].1 = r"AppData\Local\Cuprate";
|
||||||
array[1].1 = r"AppData\Roaming\Cuprate";
|
array[1].1 = r"AppData\Roaming\Cuprate";
|
||||||
array[2].1 = r"AppData\Roaming\Cuprate";
|
array[2].1 = r"AppData\Roaming\Cuprate";
|
||||||
array[3].1 = r"AppData\Roaming\Cuprate\blockchain";
|
|
||||||
array[4].1 = r"AppData\Roaming\Cuprate\txpool";
|
|
||||||
} else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
array[0].1 = "Library/Caches/Cuprate";
|
array[0].1 = "Library/Caches/Cuprate";
|
||||||
array[1].1 = "Library/Application Support/Cuprate";
|
array[1].1 = "Library/Application Support/Cuprate";
|
||||||
array[2].1 = "Library/Application Support/Cuprate";
|
array[2].1 = "Library/Application Support/Cuprate";
|
||||||
array[3].1 = "Library/Application Support/Cuprate/blockchain";
|
|
||||||
array[4].1 = "Library/Application Support/Cuprate/txpool";
|
|
||||||
} else {
|
} else {
|
||||||
// Assumes Linux.
|
// Assumes Linux.
|
||||||
array[0].1 = ".cache/cuprate";
|
array[0].1 = ".cache/cuprate";
|
||||||
array[1].1 = ".config/cuprate";
|
array[1].1 = ".config/cuprate";
|
||||||
array[2].1 = ".local/share/cuprate";
|
array[2].1 = ".local/share/cuprate";
|
||||||
array[3].1 = ".local/share/cuprate/blockchain";
|
|
||||||
array[4].1 = ".local/share/cuprate/txpool";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (path, expected) in array {
|
for (path, expected) in array {
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
//! into it's own crate.
|
//! into it's own crate.
|
||||||
//!
|
//!
|
||||||
//! `#[no_std]` compatible.
|
//! `#[no_std]` compatible.
|
||||||
|
// TODO: move to types crate.
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
const MAINNET_NETWORK_ID: [u8; 16] = [
|
const MAINNET_NETWORK_ID: [u8; 16] = [
|
||||||
0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10,
|
0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10,
|
||||||
|
@ -17,7 +23,8 @@ const STAGENET_NETWORK_ID: [u8; 16] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/// An enum representing every Monero network.
|
/// An enum representing every Monero network.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum Network {
|
pub enum Network {
|
||||||
/// Mainnet
|
/// Mainnet
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -38,3 +45,28 @@ impl Network {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ParseNetworkError;
|
||||||
|
|
||||||
|
impl FromStr for Network {
|
||||||
|
type Err = ParseNetworkError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"mainnet" | "Mainnet" => Ok(Self::Mainnet),
|
||||||
|
"testnet" | "Testnet" => Ok(Self::Testnet),
|
||||||
|
"stagenet" | "Stagenet" => Ok(Self::Stagenet),
|
||||||
|
_ => Err(ParseNetworkError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for Network {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::Mainnet => "mainnet",
|
||||||
|
Self::Testnet => "testnet",
|
||||||
|
Self::Stagenet => "stagenet",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -76,14 +76,14 @@ macro_rules! epee_object {
|
||||||
// All this does is return the second (right) arg if present otherwise the left is returned.
|
// All this does is return the second (right) arg if present otherwise the left is returned.
|
||||||
(
|
(
|
||||||
@internal_try_right_then_left
|
@internal_try_right_then_left
|
||||||
$a:expr, $b:expr
|
$a:expr_2021, $b:expr_2021
|
||||||
) => {
|
) => {
|
||||||
$b
|
$b
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
@internal_try_right_then_left
|
@internal_try_right_then_left
|
||||||
$a:expr,
|
$a:expr_2021,
|
||||||
) => {
|
) => {
|
||||||
$a
|
$a
|
||||||
};
|
};
|
||||||
|
@ -122,7 +122,7 @@ macro_rules! epee_object {
|
||||||
// ------------------------------------------------------------------------ Entry Point
|
// ------------------------------------------------------------------------ Entry Point
|
||||||
(
|
(
|
||||||
$obj:ident,
|
$obj:ident,
|
||||||
$($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr)? $(=> $read_fn:expr, $write_fn:expr, $should_write_fn:expr)?, )*
|
$($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr_2021)? $(=> $read_fn:expr_2021, $write_fn:expr_2021, $should_write_fn:expr_2021)?, )*
|
||||||
$(!flatten: $flat_field: ident: $flat_ty:ty ,)*
|
$(!flatten: $flat_field: ident: $flat_ty:ty ,)*
|
||||||
|
|
||||||
) => {
|
) => {
|
||||||
|
|
|
@ -159,7 +159,7 @@ epee_object!(
|
||||||
current_blockchain_height: u64,
|
current_blockchain_height: u64,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// A request for Txs we are missing from our `TxPool`
|
/// A request for txs we are missing from an incoming block.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct FluffyMissingTransactionsRequest {
|
pub struct FluffyMissingTransactionsRequest {
|
||||||
/// The Block we are missing the Txs in
|
/// The Block we are missing the Txs in
|
||||||
|
|
|
@ -23,7 +23,7 @@ indexmap = { workspace = true, features = ["std"] }
|
||||||
|
|
||||||
rand = { workspace = true, features = ["std", "std_rng"] }
|
rand = { workspace = true, features = ["std", "std_rng"] }
|
||||||
|
|
||||||
borsh = { workspace = true, features = ["derive", "std"]}
|
borsh = { workspace = true, features = ["derive", "std"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
cuprate-test-utils = { workspace = true }
|
cuprate-test-utils = { workspace = true }
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn test_cfg() -> AddressBookConfig {
|
||||||
AddressBookConfig {
|
AddressBookConfig {
|
||||||
max_white_list_length: 100,
|
max_white_list_length: 100,
|
||||||
max_gray_list_length: 500,
|
max_gray_list_length: 500,
|
||||||
peer_store_file: PathBuf::new(),
|
peer_store_directory: PathBuf::new(),
|
||||||
peer_save_period: Duration::from_secs(60),
|
peer_save_period: Duration::from_secs(60),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ pub struct AddressBookConfig {
|
||||||
///
|
///
|
||||||
/// Gray peers are peers we are yet to make a connection to.
|
/// Gray peers are peers we are yet to make a connection to.
|
||||||
pub max_gray_list_length: usize,
|
pub max_gray_list_length: usize,
|
||||||
/// The location to store the address book.
|
/// The location to store the peer store files.
|
||||||
pub peer_store_file: PathBuf,
|
pub peer_store_directory: PathBuf,
|
||||||
/// The amount of time between saving the address book to disk.
|
/// The amount of time between saving the address book to disk.
|
||||||
pub peer_save_period: Duration,
|
pub peer_save_period: Duration,
|
||||||
}
|
}
|
||||||
|
@ -63,11 +63,6 @@ pub enum AddressBookError {
|
||||||
pub async fn init_address_book<Z: BorshNetworkZone>(
|
pub async fn init_address_book<Z: BorshNetworkZone>(
|
||||||
cfg: AddressBookConfig,
|
cfg: AddressBookConfig,
|
||||||
) -> Result<book::AddressBook<Z>, std::io::Error> {
|
) -> Result<book::AddressBook<Z>, std::io::Error> {
|
||||||
tracing::info!(
|
|
||||||
"Loading peers from file: {} ",
|
|
||||||
cfg.peer_store_file.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
let (white_list, gray_list) = match store::read_peers_from_disk::<Z>(&cfg).await {
|
let (white_list, gray_list) = match store::read_peers_from_disk::<Z>(&cfg).await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => (vec![], vec![]),
|
Err(e) if e.kind() == ErrorKind::NotFound => (vec![], vec![]),
|
||||||
|
|
|
@ -39,7 +39,9 @@ pub(crate) fn save_peers_to_disk<Z: BorshNetworkZone>(
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let file = cfg.peer_store_file.clone();
|
let file = cfg
|
||||||
|
.peer_store_directory
|
||||||
|
.join(format!("{}_p2p_state", Z::NAME));
|
||||||
spawn_blocking(move || fs::write(&file, &data))
|
spawn_blocking(move || fs::write(&file, &data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,12 @@ pub(crate) async fn read_peers_from_disk<Z: BorshNetworkZone>(
|
||||||
),
|
),
|
||||||
std::io::Error,
|
std::io::Error,
|
||||||
> {
|
> {
|
||||||
let file = cfg.peer_store_file.clone();
|
let file = cfg
|
||||||
|
.peer_store_directory
|
||||||
|
.join(format!("{}_p2p_state", Z::NAME));
|
||||||
|
|
||||||
|
tracing::info!("Loading peers from file: {} ", file.display());
|
||||||
|
|
||||||
let data = spawn_blocking(move || fs::read(file)).await.unwrap()?;
|
let data = spawn_blocking(move || fs::read(file)).await.unwrap()?;
|
||||||
|
|
||||||
let de_ser: DeserPeerDataV1<Z::Addr> = from_slice(&data)?;
|
let de_ser: DeserPeerDataV1<Z::Addr> = from_slice(&data)?;
|
||||||
|
|
|
@ -157,7 +157,7 @@ pub struct BufferSinkSend<'a, T> {
|
||||||
item: Option<T>,
|
item: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Future for BufferSinkSend<'a, T> {
|
impl<T> Future for BufferSinkSend<'_, T> {
|
||||||
type Output = Result<(), BufferError>;
|
type Output = Result<(), BufferError>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
@ -183,7 +183,7 @@ pub struct BufferSinkReady<'a, T> {
|
||||||
size_needed: usize,
|
size_needed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Future for BufferSinkReady<'a, T> {
|
impl<T> Future for BufferSinkReady<'_, T> {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
OutboundPeer, State,
|
OutboundPeer, State,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::type_complexity)]
|
||||||
pub(crate) fn mock_discover_svc<Req: Send + 'static>() -> (
|
pub(crate) fn mock_discover_svc<Req: Send + 'static>() -> (
|
||||||
impl Stream<
|
impl Stream<
|
||||||
Item = Result<
|
Item = Result<
|
||||||
|
|
|
@ -27,9 +27,11 @@ mod connector;
|
||||||
pub mod handshaker;
|
pub mod handshaker;
|
||||||
mod request_handler;
|
mod request_handler;
|
||||||
mod timeout_monitor;
|
mod timeout_monitor;
|
||||||
|
mod weak;
|
||||||
|
|
||||||
pub use connector::{ConnectRequest, Connector};
|
pub use connector::{ConnectRequest, Connector};
|
||||||
pub use handshaker::{DoHandshakeRequest, HandshakeError, HandshakerBuilder};
|
pub use handshaker::{DoHandshakeRequest, HandshakeError, HandshakerBuilder};
|
||||||
|
pub use weak::WeakClient;
|
||||||
|
|
||||||
/// An internal identifier for a given peer, will be their address if known
|
/// An internal identifier for a given peer, will be their address if known
|
||||||
/// or a random u128 if not.
|
/// or a random u128 if not.
|
||||||
|
@ -130,6 +132,17 @@ impl<Z: NetworkZone> Client<Z> {
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`WeakClient`] for this [`Client`].
|
||||||
|
pub fn downgrade(&self) -> WeakClient<Z> {
|
||||||
|
WeakClient {
|
||||||
|
info: self.info.clone(),
|
||||||
|
connection_tx: self.connection_tx.downgrade(),
|
||||||
|
semaphore: self.semaphore.clone(),
|
||||||
|
permit: None,
|
||||||
|
error: self.error.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Z: NetworkZone> Service<PeerRequest> for Client<Z> {
|
impl<Z: NetworkZone> Service<PeerRequest> for Client<Z> {
|
||||||
|
|
114
p2p/p2p-core/src/client/weak.rs
Normal file
114
p2p/p2p-core/src/client/weak.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
use std::task::{ready, Context, Poll};
|
||||||
|
|
||||||
|
use futures::channel::oneshot;
|
||||||
|
use tokio::sync::{mpsc, OwnedSemaphorePermit};
|
||||||
|
use tokio_util::sync::PollSemaphore;
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
client::{connection, PeerInformation},
|
||||||
|
NetworkZone, PeerError, PeerRequest, PeerResponse, SharedError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A weak handle to a [`Client`](super::Client).
|
||||||
|
///
|
||||||
|
/// When this is dropped the peer will not be disconnected.
|
||||||
|
pub struct WeakClient<N: NetworkZone> {
|
||||||
|
/// Information on the connected peer.
|
||||||
|
pub info: PeerInformation<N::Addr>,
|
||||||
|
|
||||||
|
/// The channel to the [`Connection`](connection::Connection) task.
|
||||||
|
pub(super) connection_tx: mpsc::WeakSender<connection::ConnectionTaskRequest>,
|
||||||
|
|
||||||
|
/// The semaphore that limits the requests sent to the peer.
|
||||||
|
pub(super) semaphore: PollSemaphore,
|
||||||
|
/// A permit for the semaphore, will be [`Some`] after `poll_ready` returns ready.
|
||||||
|
pub(super) permit: Option<OwnedSemaphorePermit>,
|
||||||
|
|
||||||
|
/// The error slot shared between the [`Client`] and [`Connection`](connection::Connection).
|
||||||
|
pub(super) error: SharedError<PeerError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> WeakClient<N> {
|
||||||
|
/// Internal function to set an error on the [`SharedError`].
|
||||||
|
fn set_err(&self, err: PeerError) -> tower::BoxError {
|
||||||
|
let err_str = err.to_string();
|
||||||
|
match self.error.try_insert_err(err) {
|
||||||
|
Ok(()) => err_str,
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Z: NetworkZone> Service<PeerRequest> for WeakClient<Z> {
|
||||||
|
type Response = PeerResponse;
|
||||||
|
type Error = tower::BoxError;
|
||||||
|
type Future = InfallibleOneshotReceiver<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
if let Some(err) = self.error.try_get_err() {
|
||||||
|
return Poll::Ready(Err(err.to_string().into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.connection_tx.strong_count() == 0 {
|
||||||
|
let err = self.set_err(PeerError::ClientChannelClosed);
|
||||||
|
return Poll::Ready(Err(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.permit.is_some() {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let permit = ready!(self.semaphore.poll_acquire(cx))
|
||||||
|
.expect("Client semaphore should not be closed!");
|
||||||
|
|
||||||
|
self.permit = Some(permit);
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::significant_drop_tightening)]
|
||||||
|
fn call(&mut self, request: PeerRequest) -> Self::Future {
|
||||||
|
let permit = self
|
||||||
|
.permit
|
||||||
|
.take()
|
||||||
|
.expect("poll_ready did not return ready before call to call");
|
||||||
|
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let req = connection::ConnectionTaskRequest {
|
||||||
|
response_channel: tx,
|
||||||
|
request,
|
||||||
|
permit: Some(permit),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.connection_tx.upgrade() {
|
||||||
|
None => {
|
||||||
|
self.set_err(PeerError::ClientChannelClosed);
|
||||||
|
|
||||||
|
let resp = Err(PeerError::ClientChannelClosed.into());
|
||||||
|
drop(req.response_channel.send(resp));
|
||||||
|
}
|
||||||
|
Some(sender) => {
|
||||||
|
if let Err(e) = sender.try_send(req) {
|
||||||
|
// The connection task could have closed between a call to `poll_ready` and the call to
|
||||||
|
// `call`, which means if we don't handle the error here the receiver would panic.
|
||||||
|
use mpsc::error::TrySendError;
|
||||||
|
|
||||||
|
match e {
|
||||||
|
TrySendError::Closed(req) | TrySendError::Full(req) => {
|
||||||
|
self.set_err(PeerError::ClientChannelClosed);
|
||||||
|
|
||||||
|
let resp = Err(PeerError::ClientChannelClosed.into());
|
||||||
|
drop(req.response_channel.send(resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.into()
|
||||||
|
}
|
||||||
|
}
|
|
@ -121,7 +121,6 @@ pub trait NetZoneAddress:
|
||||||
///
|
///
|
||||||
/// - TODO: IP zone banning?
|
/// - TODO: IP zone banning?
|
||||||
/// - TODO: rename this to Host.
|
/// - TODO: rename this to Host.
|
||||||
|
|
||||||
type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
|
type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
|
||||||
|
|
||||||
/// Changes the port of this address to `port`.
|
/// Changes the port of this address to `port`.
|
||||||
|
|
|
@ -116,6 +116,7 @@ pub enum ProtocolResponse {
|
||||||
GetChain(ChainResponse),
|
GetChain(ChainResponse),
|
||||||
NewFluffyBlock(NewFluffyBlock),
|
NewFluffyBlock(NewFluffyBlock),
|
||||||
NewTransactions(NewTransactions),
|
NewTransactions(NewTransactions),
|
||||||
|
FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest),
|
||||||
NA,
|
NA,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +140,9 @@ impl PeerResponse {
|
||||||
ProtocolResponse::GetChain(_) => MessageID::GetChain,
|
ProtocolResponse::GetChain(_) => MessageID::GetChain,
|
||||||
ProtocolResponse::NewFluffyBlock(_) => MessageID::NewBlock,
|
ProtocolResponse::NewFluffyBlock(_) => MessageID::NewBlock,
|
||||||
ProtocolResponse::NewTransactions(_) => MessageID::NewFluffyBlock,
|
ProtocolResponse::NewTransactions(_) => MessageID::NewFluffyBlock,
|
||||||
|
ProtocolResponse::FluffyMissingTransactionsRequest(_) => {
|
||||||
|
MessageID::FluffyMissingTxs
|
||||||
|
}
|
||||||
|
|
||||||
ProtocolResponse::NA => return None,
|
ProtocolResponse::NA => return None,
|
||||||
},
|
},
|
||||||
|
|
|
@ -71,6 +71,9 @@ impl TryFrom<ProtocolResponse> for ProtocolMessage {
|
||||||
ProtocolResponse::NewFluffyBlock(val) => Self::NewFluffyBlock(val),
|
ProtocolResponse::NewFluffyBlock(val) => Self::NewFluffyBlock(val),
|
||||||
ProtocolResponse::GetChain(val) => Self::ChainEntryResponse(val),
|
ProtocolResponse::GetChain(val) => Self::ChainEntryResponse(val),
|
||||||
ProtocolResponse::GetObjects(val) => Self::GetObjectsResponse(val),
|
ProtocolResponse::GetObjects(val) => Self::GetObjectsResponse(val),
|
||||||
|
ProtocolResponse::FluffyMissingTransactionsRequest(val) => {
|
||||||
|
Self::FluffyMissingTransactionsRequest(val)
|
||||||
|
}
|
||||||
ProtocolResponse::NA => return Err(MessageConversionError),
|
ProtocolResponse::NA => return Err(MessageConversionError),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ monero-serai = { workspace = true, features = ["std"] }
|
||||||
|
|
||||||
tower = { workspace = true, features = ["buffer"] }
|
tower = { workspace = true, features = ["buffer"] }
|
||||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||||
rayon = { workspace = true }
|
|
||||||
tokio-util = { workspace = true }
|
tokio-util = { workspace = true }
|
||||||
|
rayon = { workspace = true }
|
||||||
tokio-stream = { workspace = true, features = ["sync", "time"] }
|
tokio-stream = { workspace = true, features = ["sync", "time"] }
|
||||||
futures = { workspace = true, features = ["std"] }
|
futures = { workspace = true, features = ["std"] }
|
||||||
pin-project = { workspace = true }
|
pin-project = { workspace = true }
|
||||||
dashmap = { workspace = true }
|
indexmap = { workspace = true, features = ["std"] }
|
||||||
|
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
bytes = { workspace = true, features = ["std"] }
|
bytes = { workspace = true, features = ["std"] }
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{max, min, Reverse},
|
cmp::{max, min, Reverse},
|
||||||
collections::{BTreeMap, BinaryHeap},
|
collections::{BTreeMap, BinaryHeap},
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ use tokio::{
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
time::{interval, timeout, MissedTickBehavior},
|
time::{interval, timeout, MissedTickBehavior},
|
||||||
};
|
};
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{util::BoxCloneService, Service, ServiceExt};
|
||||||
use tracing::{instrument, Instrument, Span};
|
use tracing::{instrument, Instrument, Span};
|
||||||
|
|
||||||
use cuprate_async_buffer::{BufferAppender, BufferStream};
|
use cuprate_async_buffer::{BufferAppender, BufferStream};
|
||||||
|
@ -27,11 +26,11 @@ use cuprate_p2p_core::{handles::ConnectionHandle, NetworkZone};
|
||||||
use cuprate_pruning::PruningSeed;
|
use cuprate_pruning::PruningSeed;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client_pool::{ClientPool, ClientPoolDropGuard},
|
|
||||||
constants::{
|
constants::{
|
||||||
BLOCK_DOWNLOADER_REQUEST_TIMEOUT, EMPTY_CHAIN_ENTRIES_BEFORE_TOP_ASSUMED, LONG_BAN,
|
BLOCK_DOWNLOADER_REQUEST_TIMEOUT, EMPTY_CHAIN_ENTRIES_BEFORE_TOP_ASSUMED, LONG_BAN,
|
||||||
MAX_BLOCK_BATCH_LEN, MAX_DOWNLOAD_FAILURES,
|
MAX_BLOCK_BATCH_LEN, MAX_DOWNLOAD_FAILURES,
|
||||||
},
|
},
|
||||||
|
peer_set::ClientDropGuard,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod block_queue;
|
mod block_queue;
|
||||||
|
@ -41,6 +40,7 @@ mod request_chain;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
use crate::peer_set::{PeerSetRequest, PeerSetResponse};
|
||||||
use block_queue::{BlockQueue, ReadyQueueBatch};
|
use block_queue::{BlockQueue, ReadyQueueBatch};
|
||||||
use chain_tracker::{BlocksToRetrieve, ChainEntry, ChainTracker};
|
use chain_tracker::{BlocksToRetrieve, ChainEntry, ChainTracker};
|
||||||
use download_batch::download_batch_task;
|
use download_batch::download_batch_task;
|
||||||
|
@ -62,15 +62,15 @@ pub struct BlockBatch {
|
||||||
pub struct BlockDownloaderConfig {
|
pub struct BlockDownloaderConfig {
|
||||||
/// The size in bytes of the buffer between the block downloader and the place which
|
/// The size in bytes of the buffer between the block downloader and the place which
|
||||||
/// is consuming the downloaded blocks.
|
/// is consuming the downloaded blocks.
|
||||||
pub buffer_size: usize,
|
pub buffer_bytes: usize,
|
||||||
/// The size of the in progress queue (in bytes) at which we stop requesting more blocks.
|
/// The size of the in progress queue (in bytes) at which we stop requesting more blocks.
|
||||||
pub in_progress_queue_size: usize,
|
pub in_progress_queue_bytes: usize,
|
||||||
/// The [`Duration`] between checking the client pool for free peers.
|
/// The [`Duration`] between checking the client pool for free peers.
|
||||||
pub check_client_pool_interval: Duration,
|
pub check_client_pool_interval: Duration,
|
||||||
/// The target size of a single batch of blocks (in bytes).
|
/// The target size of a single batch of blocks (in bytes).
|
||||||
pub target_batch_size: usize,
|
pub target_batch_bytes: usize,
|
||||||
/// The initial amount of blocks to request (in number of blocks)
|
/// The initial amount of blocks to request (in number of blocks)
|
||||||
pub initial_batch_size: usize,
|
pub initial_batch_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurred in the [`BlockDownloader`].
|
/// An error that occurred in the [`BlockDownloader`].
|
||||||
|
@ -135,7 +135,7 @@ pub enum ChainSvcResponse {
|
||||||
/// call this function again, so it can start the search again.
|
/// call this function again, so it can start the search again.
|
||||||
#[instrument(level = "error", skip_all, name = "block_downloader")]
|
#[instrument(level = "error", skip_all, name = "block_downloader")]
|
||||||
pub fn download_blocks<N: NetworkZone, C>(
|
pub fn download_blocks<N: NetworkZone, C>(
|
||||||
client_pool: Arc<ClientPool<N>>,
|
peer_set: BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
|
||||||
our_chain_svc: C,
|
our_chain_svc: C,
|
||||||
config: BlockDownloaderConfig,
|
config: BlockDownloaderConfig,
|
||||||
) -> BufferStream<BlockBatch>
|
) -> BufferStream<BlockBatch>
|
||||||
|
@ -145,10 +145,9 @@ where
|
||||||
+ 'static,
|
+ 'static,
|
||||||
C::Future: Send + 'static,
|
C::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
let (buffer_appender, buffer_stream) = cuprate_async_buffer::new_buffer(config.buffer_size);
|
let (buffer_appender, buffer_stream) = cuprate_async_buffer::new_buffer(config.buffer_bytes);
|
||||||
|
|
||||||
let block_downloader =
|
let block_downloader = BlockDownloader::new(peer_set, our_chain_svc, buffer_appender, config);
|
||||||
BlockDownloader::new(client_pool, our_chain_svc, buffer_appender, config);
|
|
||||||
|
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
block_downloader
|
block_downloader
|
||||||
|
@ -186,8 +185,8 @@ where
|
||||||
/// - download an already requested batch of blocks (this might happen due to an error in the previous request
|
/// - download an already requested batch of blocks (this might happen due to an error in the previous request
|
||||||
/// or because the queue of ready blocks is too large, so we need the oldest block to clear it).
|
/// or because the queue of ready blocks is too large, so we need the oldest block to clear it).
|
||||||
struct BlockDownloader<N: NetworkZone, C> {
|
struct BlockDownloader<N: NetworkZone, C> {
|
||||||
/// The client pool.
|
/// The peer set.
|
||||||
client_pool: Arc<ClientPool<N>>,
|
peer_set: BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
|
||||||
|
|
||||||
/// The service that holds our current chain state.
|
/// The service that holds our current chain state.
|
||||||
our_chain_svc: C,
|
our_chain_svc: C,
|
||||||
|
@ -208,7 +207,7 @@ struct BlockDownloader<N: NetworkZone, C> {
|
||||||
///
|
///
|
||||||
/// Returns a result of the chain entry or an error.
|
/// Returns a result of the chain entry or an error.
|
||||||
#[expect(clippy::type_complexity)]
|
#[expect(clippy::type_complexity)]
|
||||||
chain_entry_task: JoinSet<Result<(ClientPoolDropGuard<N>, ChainEntry<N>), BlockDownloadError>>,
|
chain_entry_task: JoinSet<Result<(ClientDropGuard<N>, ChainEntry<N>), BlockDownloadError>>,
|
||||||
|
|
||||||
/// The current inflight requests.
|
/// The current inflight requests.
|
||||||
///
|
///
|
||||||
|
@ -235,15 +234,15 @@ where
|
||||||
{
|
{
|
||||||
/// Creates a new [`BlockDownloader`]
|
/// Creates a new [`BlockDownloader`]
|
||||||
fn new(
|
fn new(
|
||||||
client_pool: Arc<ClientPool<N>>,
|
peer_set: BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
|
||||||
our_chain_svc: C,
|
our_chain_svc: C,
|
||||||
buffer_appender: BufferAppender<BlockBatch>,
|
buffer_appender: BufferAppender<BlockBatch>,
|
||||||
config: BlockDownloaderConfig,
|
config: BlockDownloaderConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client_pool,
|
peer_set,
|
||||||
our_chain_svc,
|
our_chain_svc,
|
||||||
amount_of_blocks_to_request: config.initial_batch_size,
|
amount_of_blocks_to_request: config.initial_batch_len,
|
||||||
amount_of_blocks_to_request_updated_at: 0,
|
amount_of_blocks_to_request_updated_at: 0,
|
||||||
amount_of_empty_chain_entries: 0,
|
amount_of_empty_chain_entries: 0,
|
||||||
block_download_tasks: JoinSet::new(),
|
block_download_tasks: JoinSet::new(),
|
||||||
|
@ -259,7 +258,7 @@ where
|
||||||
fn check_pending_peers(
|
fn check_pending_peers(
|
||||||
&mut self,
|
&mut self,
|
||||||
chain_tracker: &mut ChainTracker<N>,
|
chain_tracker: &mut ChainTracker<N>,
|
||||||
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientPoolDropGuard<N>>>,
|
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientDropGuard<N>>>,
|
||||||
) {
|
) {
|
||||||
tracing::debug!("Checking if we can give any work to pending peers.");
|
tracing::debug!("Checking if we can give any work to pending peers.");
|
||||||
|
|
||||||
|
@ -286,11 +285,11 @@ where
|
||||||
/// This function will find the batch(es) that we are waiting on to clear our ready queue and sends another request
|
/// This function will find the batch(es) that we are waiting on to clear our ready queue and sends another request
|
||||||
/// for them.
|
/// for them.
|
||||||
///
|
///
|
||||||
/// Returns the [`ClientPoolDropGuard`] back if it doesn't have the batch according to its pruning seed.
|
/// Returns the [`ClientDropGuard`] back if it doesn't have the batch according to its pruning seed.
|
||||||
fn request_inflight_batch_again(
|
fn request_inflight_batch_again(
|
||||||
&mut self,
|
&mut self,
|
||||||
client: ClientPoolDropGuard<N>,
|
client: ClientDropGuard<N>,
|
||||||
) -> Option<ClientPoolDropGuard<N>> {
|
) -> Option<ClientDropGuard<N>> {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Requesting an inflight batch, current ready queue size: {}",
|
"Requesting an inflight batch, current ready queue size: {}",
|
||||||
self.block_queue.size()
|
self.block_queue.size()
|
||||||
|
@ -336,13 +335,13 @@ where
|
||||||
///
|
///
|
||||||
/// The batch requested will depend on our current state, failed batches will be prioritised.
|
/// The batch requested will depend on our current state, failed batches will be prioritised.
|
||||||
///
|
///
|
||||||
/// Returns the [`ClientPoolDropGuard`] back if it doesn't have the data we currently need according
|
/// Returns the [`ClientDropGuard`] back if it doesn't have the data we currently need according
|
||||||
/// to its pruning seed.
|
/// to its pruning seed.
|
||||||
fn request_block_batch(
|
fn request_block_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
chain_tracker: &mut ChainTracker<N>,
|
chain_tracker: &mut ChainTracker<N>,
|
||||||
client: ClientPoolDropGuard<N>,
|
client: ClientDropGuard<N>,
|
||||||
) -> Option<ClientPoolDropGuard<N>> {
|
) -> Option<ClientDropGuard<N>> {
|
||||||
tracing::trace!("Using peer to request a batch of blocks.");
|
tracing::trace!("Using peer to request a batch of blocks.");
|
||||||
// First look to see if we have any failed requests.
|
// First look to see if we have any failed requests.
|
||||||
while let Some(failed_request) = self.failed_batches.peek() {
|
while let Some(failed_request) = self.failed_batches.peek() {
|
||||||
|
@ -382,7 +381,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our ready queue is too large send duplicate requests for the blocks we are waiting on.
|
// If our ready queue is too large send duplicate requests for the blocks we are waiting on.
|
||||||
if self.block_queue.size() >= self.config.in_progress_queue_size {
|
if self.block_queue.size() >= self.config.in_progress_queue_bytes {
|
||||||
return self.request_inflight_batch_again(client);
|
return self.request_inflight_batch_again(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,13 +415,13 @@ where
|
||||||
/// This function will use our current state to decide if we should send a request for a chain entry
|
/// This function will use our current state to decide if we should send a request for a chain entry
|
||||||
/// or if we should request a batch of blocks.
|
/// or if we should request a batch of blocks.
|
||||||
///
|
///
|
||||||
/// Returns the [`ClientPoolDropGuard`] back if it doesn't have the data we currently need according
|
/// Returns the [`ClientDropGuard`] back if it doesn't have the data we currently need according
|
||||||
/// to its pruning seed.
|
/// to its pruning seed.
|
||||||
fn try_handle_free_client(
|
fn try_handle_free_client(
|
||||||
&mut self,
|
&mut self,
|
||||||
chain_tracker: &mut ChainTracker<N>,
|
chain_tracker: &mut ChainTracker<N>,
|
||||||
client: ClientPoolDropGuard<N>,
|
client: ClientDropGuard<N>,
|
||||||
) -> Option<ClientPoolDropGuard<N>> {
|
) -> Option<ClientDropGuard<N>> {
|
||||||
// We send 2 requests, so if one of them is slow or doesn't have the next chain, we still have a backup.
|
// We send 2 requests, so if one of them is slow or doesn't have the next chain, we still have a backup.
|
||||||
if self.chain_entry_task.len() < 2
|
if self.chain_entry_task.len() < 2
|
||||||
// If we have had too many failures then assume the tip has been found so no more chain entries.
|
// If we have had too many failures then assume the tip has been found so no more chain entries.
|
||||||
|
@ -463,7 +462,7 @@ where
|
||||||
async fn check_for_free_clients(
|
async fn check_for_free_clients(
|
||||||
&mut self,
|
&mut self,
|
||||||
chain_tracker: &mut ChainTracker<N>,
|
chain_tracker: &mut ChainTracker<N>,
|
||||||
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientPoolDropGuard<N>>>,
|
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientDropGuard<N>>>,
|
||||||
) -> Result<(), BlockDownloadError> {
|
) -> Result<(), BlockDownloadError> {
|
||||||
tracing::debug!("Checking for free peers");
|
tracing::debug!("Checking for free peers");
|
||||||
|
|
||||||
|
@ -478,10 +477,19 @@ where
|
||||||
panic!("Chain service returned wrong response.");
|
panic!("Chain service returned wrong response.");
|
||||||
};
|
};
|
||||||
|
|
||||||
for client in self
|
let PeerSetResponse::PeersWithMorePoW(clients) = self
|
||||||
.client_pool
|
.peer_set
|
||||||
.clients_with_more_cumulative_difficulty(current_cumulative_difficulty)
|
.ready()
|
||||||
{
|
.await?
|
||||||
|
.call(PeerSetRequest::PeersWithMorePoW(
|
||||||
|
current_cumulative_difficulty,
|
||||||
|
))
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
for client in clients {
|
||||||
pending_peers
|
pending_peers
|
||||||
.entry(client.info.pruning_seed)
|
.entry(client.info.pruning_seed)
|
||||||
.or_default()
|
.or_default()
|
||||||
|
@ -497,9 +505,9 @@ where
|
||||||
async fn handle_download_batch_res(
|
async fn handle_download_batch_res(
|
||||||
&mut self,
|
&mut self,
|
||||||
start_height: usize,
|
start_height: usize,
|
||||||
res: Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError>,
|
res: Result<(ClientDropGuard<N>, BlockBatch), BlockDownloadError>,
|
||||||
chain_tracker: &mut ChainTracker<N>,
|
chain_tracker: &mut ChainTracker<N>,
|
||||||
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientPoolDropGuard<N>>>,
|
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientDropGuard<N>>>,
|
||||||
) -> Result<(), BlockDownloadError> {
|
) -> Result<(), BlockDownloadError> {
|
||||||
tracing::debug!("Handling block download response");
|
tracing::debug!("Handling block download response");
|
||||||
|
|
||||||
|
@ -557,7 +565,7 @@ where
|
||||||
self.amount_of_blocks_to_request = calculate_next_block_batch_size(
|
self.amount_of_blocks_to_request = calculate_next_block_batch_size(
|
||||||
block_batch.size,
|
block_batch.size,
|
||||||
block_batch.blocks.len(),
|
block_batch.blocks.len(),
|
||||||
self.config.target_batch_size,
|
self.config.target_batch_bytes,
|
||||||
);
|
);
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
@ -593,7 +601,7 @@ where
|
||||||
/// Starts the main loop of the block downloader.
|
/// Starts the main loop of the block downloader.
|
||||||
async fn run(mut self) -> Result<(), BlockDownloadError> {
|
async fn run(mut self) -> Result<(), BlockDownloadError> {
|
||||||
let mut chain_tracker =
|
let mut chain_tracker =
|
||||||
initial_chain_search(&self.client_pool, &mut self.our_chain_svc).await?;
|
initial_chain_search(&mut self.peer_set, &mut self.our_chain_svc).await?;
|
||||||
|
|
||||||
let mut pending_peers = BTreeMap::new();
|
let mut pending_peers = BTreeMap::new();
|
||||||
|
|
||||||
|
@ -662,7 +670,7 @@ struct BlockDownloadTaskResponse<N: NetworkZone> {
|
||||||
/// The start height of the batch.
|
/// The start height of the batch.
|
||||||
start_height: usize,
|
start_height: usize,
|
||||||
/// A result containing the batch or an error.
|
/// A result containing the batch or an error.
|
||||||
result: Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError>,
|
result: Result<(ClientDropGuard<N>, BlockBatch), BlockDownloadError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns if a peer has all the blocks in a range, according to its [`PruningSeed`].
|
/// Returns if a peer has all the blocks in a range, according to its [`PruningSeed`].
|
||||||
|
|
|
@ -16,8 +16,8 @@ use cuprate_wire::protocol::{GetObjectsRequest, GetObjectsResponse};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block_downloader::{BlockBatch, BlockDownloadError, BlockDownloadTaskResponse},
|
block_downloader::{BlockBatch, BlockDownloadError, BlockDownloadTaskResponse},
|
||||||
client_pool::ClientPoolDropGuard,
|
|
||||||
constants::{BLOCK_DOWNLOADER_REQUEST_TIMEOUT, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN},
|
constants::{BLOCK_DOWNLOADER_REQUEST_TIMEOUT, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN},
|
||||||
|
peer_set::ClientDropGuard,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Attempts to request a batch of blocks from a peer, returning [`BlockDownloadTaskResponse`].
|
/// Attempts to request a batch of blocks from a peer, returning [`BlockDownloadTaskResponse`].
|
||||||
|
@ -32,7 +32,7 @@ use crate::{
|
||||||
)]
|
)]
|
||||||
#[expect(clippy::used_underscore_binding)]
|
#[expect(clippy::used_underscore_binding)]
|
||||||
pub async fn download_batch_task<N: NetworkZone>(
|
pub async fn download_batch_task<N: NetworkZone>(
|
||||||
client: ClientPoolDropGuard<N>,
|
client: ClientDropGuard<N>,
|
||||||
ids: ByteArrayVec<32>,
|
ids: ByteArrayVec<32>,
|
||||||
previous_id: [u8; 32],
|
previous_id: [u8; 32],
|
||||||
expected_start_height: usize,
|
expected_start_height: usize,
|
||||||
|
@ -49,11 +49,11 @@ pub async fn download_batch_task<N: NetworkZone>(
|
||||||
/// This function will validate the blocks that were downloaded were the ones asked for and that they match
|
/// This function will validate the blocks that were downloaded were the ones asked for and that they match
|
||||||
/// the expected height.
|
/// the expected height.
|
||||||
async fn request_batch_from_peer<N: NetworkZone>(
|
async fn request_batch_from_peer<N: NetworkZone>(
|
||||||
mut client: ClientPoolDropGuard<N>,
|
mut client: ClientDropGuard<N>,
|
||||||
ids: ByteArrayVec<32>,
|
ids: ByteArrayVec<32>,
|
||||||
previous_id: [u8; 32],
|
previous_id: [u8; 32],
|
||||||
expected_start_height: usize,
|
expected_start_height: usize,
|
||||||
) -> Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError> {
|
) -> Result<(ClientDropGuard<N>, BlockBatch), BlockDownloadError> {
|
||||||
let request = PeerRequest::Protocol(ProtocolRequest::GetObjects(GetObjectsRequest {
|
let request = PeerRequest::Protocol(ProtocolRequest::GetObjects(GetObjectsRequest {
|
||||||
blocks: ids.clone(),
|
blocks: ids.clone(),
|
||||||
pruned: false,
|
pruned: false,
|
||||||
|
@ -146,9 +146,9 @@ fn deserialize_batch(
|
||||||
|
|
||||||
// Check the height lines up as expected.
|
// Check the height lines up as expected.
|
||||||
// This must happen after the hash check.
|
// This must happen after the hash check.
|
||||||
if !block
|
if block
|
||||||
.number()
|
.number()
|
||||||
.is_some_and(|height| height == expected_height)
|
.is_none_or(|height| height != expected_height)
|
||||||
{
|
{
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Invalid chain, expected height: {expected_height}, got height: {:?}",
|
"Invalid chain, expected height: {expected_height}, got height: {:?}",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{mem, sync::Arc};
|
use std::mem;
|
||||||
|
|
||||||
use tokio::{task::JoinSet, time::timeout};
|
use tokio::{task::JoinSet, time::timeout};
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{util::BoxCloneService, Service, ServiceExt};
|
||||||
use tracing::{instrument, Instrument, Span};
|
use tracing::{instrument, Instrument, Span};
|
||||||
|
|
||||||
use cuprate_p2p_core::{
|
use cuprate_p2p_core::{
|
||||||
|
@ -15,11 +15,11 @@ use crate::{
|
||||||
chain_tracker::{ChainEntry, ChainTracker},
|
chain_tracker::{ChainEntry, ChainTracker},
|
||||||
BlockDownloadError, ChainSvcRequest, ChainSvcResponse,
|
BlockDownloadError, ChainSvcRequest, ChainSvcResponse,
|
||||||
},
|
},
|
||||||
client_pool::{ClientPool, ClientPoolDropGuard},
|
|
||||||
constants::{
|
constants::{
|
||||||
BLOCK_DOWNLOADER_REQUEST_TIMEOUT, INITIAL_CHAIN_REQUESTS_TO_SEND,
|
BLOCK_DOWNLOADER_REQUEST_TIMEOUT, INITIAL_CHAIN_REQUESTS_TO_SEND,
|
||||||
MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MEDIUM_BAN,
|
MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MEDIUM_BAN,
|
||||||
},
|
},
|
||||||
|
peer_set::{ClientDropGuard, PeerSetRequest, PeerSetResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Request a chain entry from a peer.
|
/// Request a chain entry from a peer.
|
||||||
|
@ -27,9 +27,9 @@ use crate::{
|
||||||
/// Because the block downloader only follows and downloads one chain we only have to send the block hash of
|
/// Because the block downloader only follows and downloads one chain we only have to send the block hash of
|
||||||
/// top block we have found and the genesis block, this is then called `short_history`.
|
/// top block we have found and the genesis block, this is then called `short_history`.
|
||||||
pub(crate) async fn request_chain_entry_from_peer<N: NetworkZone>(
|
pub(crate) async fn request_chain_entry_from_peer<N: NetworkZone>(
|
||||||
mut client: ClientPoolDropGuard<N>,
|
mut client: ClientDropGuard<N>,
|
||||||
short_history: [[u8; 32]; 2],
|
short_history: [[u8; 32]; 2],
|
||||||
) -> Result<(ClientPoolDropGuard<N>, ChainEntry<N>), BlockDownloadError> {
|
) -> Result<(ClientDropGuard<N>, ChainEntry<N>), BlockDownloadError> {
|
||||||
let PeerResponse::Protocol(ProtocolResponse::GetChain(chain_res)) = client
|
let PeerResponse::Protocol(ProtocolResponse::GetChain(chain_res)) = client
|
||||||
.ready()
|
.ready()
|
||||||
.await?
|
.await?
|
||||||
|
@ -80,7 +80,7 @@ pub(crate) async fn request_chain_entry_from_peer<N: NetworkZone>(
|
||||||
/// We then wait for their response and choose the peer who claims the highest cumulative difficulty.
|
/// We then wait for their response and choose the peer who claims the highest cumulative difficulty.
|
||||||
#[instrument(level = "error", skip_all)]
|
#[instrument(level = "error", skip_all)]
|
||||||
pub async fn initial_chain_search<N: NetworkZone, C>(
|
pub async fn initial_chain_search<N: NetworkZone, C>(
|
||||||
client_pool: &Arc<ClientPool<N>>,
|
peer_set: &mut BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
|
||||||
mut our_chain_svc: C,
|
mut our_chain_svc: C,
|
||||||
) -> Result<ChainTracker<N>, BlockDownloadError>
|
) -> Result<ChainTracker<N>, BlockDownloadError>
|
||||||
where
|
where
|
||||||
|
@ -102,9 +102,15 @@ where
|
||||||
|
|
||||||
let our_genesis = *block_ids.last().expect("Blockchain had no genesis block.");
|
let our_genesis = *block_ids.last().expect("Blockchain had no genesis block.");
|
||||||
|
|
||||||
let mut peers = client_pool
|
let PeerSetResponse::PeersWithMorePoW(clients) = peer_set
|
||||||
.clients_with_more_cumulative_difficulty(cumulative_difficulty)
|
.ready()
|
||||||
.into_iter();
|
.await?
|
||||||
|
.call(PeerSetRequest::PeersWithMorePoW(cumulative_difficulty))
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let mut peers = clients.into_iter();
|
||||||
|
|
||||||
let mut futs = JoinSet::new();
|
let mut futs = JoinSet::new();
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ use monero_serai::{
|
||||||
transaction::{Input, Timelock, Transaction, TransactionPrefix},
|
transaction::{Input, Timelock, Transaction, TransactionPrefix},
|
||||||
};
|
};
|
||||||
use proptest::{collection::vec, prelude::*};
|
use proptest::{collection::vec, prelude::*};
|
||||||
use tokio::time::timeout;
|
use tokio::{sync::mpsc, time::timeout};
|
||||||
use tower::{service_fn, Service};
|
use tower::{buffer::Buffer, service_fn, Service, ServiceExt};
|
||||||
|
|
||||||
use cuprate_fixed_bytes::ByteArrayVec;
|
use cuprate_fixed_bytes::ByteArrayVec;
|
||||||
use cuprate_p2p_core::{
|
use cuprate_p2p_core::{
|
||||||
|
@ -31,7 +31,7 @@ use cuprate_wire::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block_downloader::{download_blocks, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse},
|
block_downloader::{download_blocks, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse},
|
||||||
client_pool::ClientPool,
|
peer_set::PeerSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
|
@ -48,28 +48,29 @@ proptest! {
|
||||||
|
|
||||||
let tokio_pool = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
|
let tokio_pool = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
|
||||||
|
|
||||||
#[expect(clippy::significant_drop_tightening)]
|
|
||||||
tokio_pool.block_on(async move {
|
tokio_pool.block_on(async move {
|
||||||
timeout(Duration::from_secs(600), async move {
|
timeout(Duration::from_secs(600), async move {
|
||||||
let client_pool = ClientPool::new();
|
let (new_connection_tx, new_connection_rx) = mpsc::channel(peers);
|
||||||
|
|
||||||
|
let peer_set = PeerSet::new(new_connection_rx);
|
||||||
|
|
||||||
for _ in 0..peers {
|
for _ in 0..peers {
|
||||||
let client = mock_block_downloader_client(Arc::clone(&blockchain));
|
let client = mock_block_downloader_client(Arc::clone(&blockchain));
|
||||||
|
|
||||||
client_pool.add_new_client(client);
|
new_connection_tx.try_send(client).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let stream = download_blocks(
|
let stream = download_blocks(
|
||||||
client_pool,
|
Buffer::new(peer_set, 10).boxed_clone(),
|
||||||
OurChainSvc {
|
OurChainSvc {
|
||||||
genesis: *blockchain.blocks.first().unwrap().0
|
genesis: *blockchain.blocks.first().unwrap().0
|
||||||
},
|
},
|
||||||
BlockDownloaderConfig {
|
BlockDownloaderConfig {
|
||||||
buffer_size: 1_000,
|
buffer_bytes: 1_000,
|
||||||
in_progress_queue_size: 10_000,
|
in_progress_queue_bytes: 10_000,
|
||||||
check_client_pool_interval: Duration::from_secs(5),
|
check_client_pool_interval: Duration::from_secs(5),
|
||||||
target_batch_size: 5_000,
|
target_batch_bytes: 5_000,
|
||||||
initial_batch_size: 1,
|
initial_batch_len: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
let blocks = stream.map(|blocks| blocks.blocks).concat().await;
|
let blocks = stream.map(|blocks| blocks.blocks).concat().await;
|
||||||
|
|
|
@ -57,6 +57,7 @@ impl Default for BroadcastConfig {
|
||||||
/// - The [`BroadcastSvc`]
|
/// - The [`BroadcastSvc`]
|
||||||
/// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **outbound** peers.
|
/// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **outbound** peers.
|
||||||
/// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **inbound** peers.
|
/// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **inbound** peers.
|
||||||
|
#[expect(clippy::type_complexity)]
|
||||||
pub(crate) fn init_broadcast_channels<N: NetworkZone>(
|
pub(crate) fn init_broadcast_channels<N: NetworkZone>(
|
||||||
config: BroadcastConfig,
|
config: BroadcastConfig,
|
||||||
) -> (
|
) -> (
|
||||||
|
|
|
@ -1,188 +0,0 @@
|
||||||
//! # Client Pool.
|
|
||||||
//!
|
|
||||||
//! The [`ClientPool`], is a pool of currently connected peers that can be pulled from.
|
|
||||||
//! It does _not_ necessarily contain every connected peer as another place could have
|
|
||||||
//! taken a peer from the pool.
|
|
||||||
//!
|
|
||||||
//! When taking peers from the pool they are wrapped in [`ClientPoolDropGuard`], which
|
|
||||||
//! returns the peer to the pool when it is dropped.
|
|
||||||
//!
|
|
||||||
//! Internally the pool is a [`DashMap`] which means care should be taken in `async` code
|
|
||||||
//! as internally this uses blocking `RwLock`s.
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tracing::{Instrument, Span};
|
|
||||||
|
|
||||||
use cuprate_p2p_core::{
|
|
||||||
client::{Client, InternalPeerID},
|
|
||||||
handles::ConnectionHandle,
|
|
||||||
ConnectionDirection, NetworkZone,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) mod disconnect_monitor;
|
|
||||||
mod drop_guard_client;
|
|
||||||
|
|
||||||
pub use drop_guard_client::ClientPoolDropGuard;
|
|
||||||
|
|
||||||
/// The client pool, which holds currently connected free peers.
|
|
||||||
///
|
|
||||||
/// See the [module docs](self) for more.
|
|
||||||
pub struct ClientPool<N: NetworkZone> {
|
|
||||||
/// The connected [`Client`]s.
|
|
||||||
clients: DashMap<InternalPeerID<N::Addr>, Client<N>>,
|
|
||||||
/// A channel to send new peer ids down to monitor for disconnect.
|
|
||||||
new_connection_tx: mpsc::UnboundedSender<(ConnectionHandle, InternalPeerID<N::Addr>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: NetworkZone> ClientPool<N> {
|
|
||||||
/// Returns a new [`ClientPool`] wrapped in an [`Arc`].
|
|
||||||
pub fn new() -> Arc<Self> {
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
|
||||||
|
|
||||||
let pool = Arc::new(Self {
|
|
||||||
clients: DashMap::new(),
|
|
||||||
new_connection_tx: tx,
|
|
||||||
});
|
|
||||||
|
|
||||||
tokio::spawn(
|
|
||||||
disconnect_monitor::disconnect_monitor(rx, Arc::clone(&pool))
|
|
||||||
.instrument(Span::current()),
|
|
||||||
);
|
|
||||||
|
|
||||||
pool
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a [`Client`] to the pool, the client must have previously been taken from the
|
|
||||||
/// pool.
|
|
||||||
///
|
|
||||||
/// See [`ClientPool::add_new_client`] to add a [`Client`] which was not taken from the pool before.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// This function panics if `client` already exists in the pool.
|
|
||||||
fn add_client(&self, client: Client<N>) {
|
|
||||||
let handle = client.info.handle.clone();
|
|
||||||
let id = client.info.id;
|
|
||||||
|
|
||||||
// Fast path: if the client is disconnected don't add it to the peer set.
|
|
||||||
if handle.is_closed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(self.clients.insert(id, client).is_none());
|
|
||||||
|
|
||||||
// We have to check this again otherwise we could have a race condition where a
|
|
||||||
// peer is disconnected after the first check, the disconnect monitor tries to remove it,
|
|
||||||
// and then it is added to the pool.
|
|
||||||
if handle.is_closed() {
|
|
||||||
self.remove_client(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a _new_ [`Client`] to the pool, this client should be a new connection, and not already
|
|
||||||
/// from the pool.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// This function panics if `client` already exists in the pool.
|
|
||||||
pub fn add_new_client(&self, client: Client<N>) {
|
|
||||||
self.new_connection_tx
|
|
||||||
.send((client.info.handle.clone(), client.info.id))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.add_client(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a [`Client`] from the pool.
|
|
||||||
///
|
|
||||||
/// [`None`] is returned if the client did not exist in the pool.
|
|
||||||
fn remove_client(&self, peer: &InternalPeerID<N::Addr>) -> Option<Client<N>> {
|
|
||||||
self.clients.remove(peer).map(|(_, client)| client)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrows a [`Client`] from the pool.
|
|
||||||
///
|
|
||||||
/// The [`Client`] is wrapped in [`ClientPoolDropGuard`] which
|
|
||||||
/// will return the client to the pool when it's dropped.
|
|
||||||
///
|
|
||||||
/// See [`Self::borrow_clients`] for borrowing multiple clients.
|
|
||||||
pub fn borrow_client(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
peer: &InternalPeerID<N::Addr>,
|
|
||||||
) -> Option<ClientPoolDropGuard<N>> {
|
|
||||||
self.remove_client(peer).map(|client| ClientPoolDropGuard {
|
|
||||||
pool: Arc::clone(self),
|
|
||||||
client: Some(client),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrows multiple [`Client`]s from the pool.
|
|
||||||
///
|
|
||||||
/// Note that the returned iterator is not guaranteed to contain every peer asked for.
|
|
||||||
///
|
|
||||||
/// See [`Self::borrow_client`] for borrowing a single client.
|
|
||||||
pub fn borrow_clients<'a, 'b>(
|
|
||||||
self: &'a Arc<Self>,
|
|
||||||
peers: &'b [InternalPeerID<N::Addr>],
|
|
||||||
) -> impl Iterator<Item = ClientPoolDropGuard<N>> + sealed::Captures<(&'a (), &'b ())> {
|
|
||||||
peers.iter().filter_map(|peer| self.borrow_client(peer))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrows all [`Client`]s from the pool that have claimed a higher cumulative difficulty than
|
|
||||||
/// the amount passed in.
|
|
||||||
///
|
|
||||||
/// The [`Client`]s are wrapped in [`ClientPoolDropGuard`] which
|
|
||||||
/// will return the clients to the pool when they are dropped.
|
|
||||||
pub fn clients_with_more_cumulative_difficulty(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
cumulative_difficulty: u128,
|
|
||||||
) -> Vec<ClientPoolDropGuard<N>> {
|
|
||||||
let peers = self
|
|
||||||
.clients
|
|
||||||
.iter()
|
|
||||||
.filter_map(|element| {
|
|
||||||
let peer_sync_info = element.value().info.core_sync_data.lock().unwrap();
|
|
||||||
|
|
||||||
if peer_sync_info.cumulative_difficulty() > cumulative_difficulty {
|
|
||||||
Some(*element.key())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
self.borrow_clients(&peers).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks all clients in the pool checking if any claim a higher cumulative difficulty than the
|
|
||||||
/// amount specified.
|
|
||||||
pub fn contains_client_with_more_cumulative_difficulty(
|
|
||||||
&self,
|
|
||||||
cumulative_difficulty: u128,
|
|
||||||
) -> bool {
|
|
||||||
self.clients.iter().any(|element| {
|
|
||||||
let sync_data = element.value().info.core_sync_data.lock().unwrap();
|
|
||||||
sync_data.cumulative_difficulty() > cumulative_difficulty
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first outbound peer when iterating over the peers.
|
|
||||||
pub fn outbound_client(self: &Arc<Self>) -> Option<ClientPoolDropGuard<N>> {
|
|
||||||
let client = self
|
|
||||||
.clients
|
|
||||||
.iter()
|
|
||||||
.find(|element| element.value().info.direction == ConnectionDirection::Outbound)?;
|
|
||||||
let id = *client.key();
|
|
||||||
|
|
||||||
Some(self.borrow_client(&id).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod sealed {
|
|
||||||
/// TODO: Remove me when 2024 Rust
|
|
||||||
///
|
|
||||||
/// <https://rust-lang.github.io/rfcs/3498-lifetime-capture-rules-2024.html#the-captures-trick>
|
|
||||||
pub trait Captures<U> {}
|
|
||||||
|
|
||||||
impl<T: ?Sized, U> Captures<U> for T {}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
//! # Disconnect Monitor
|
|
||||||
//!
|
|
||||||
//! This module contains the [`disconnect_monitor`] task, which monitors connected peers for disconnection
|
|
||||||
//! and then removes them from the [`ClientPool`] if they do.
|
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
use cuprate_p2p_core::{client::InternalPeerID, handles::ConnectionHandle, NetworkZone};
|
|
||||||
|
|
||||||
use super::ClientPool;
|
|
||||||
|
|
||||||
/// The disconnect monitor task.
|
|
||||||
#[instrument(level = "info", skip_all)]
|
|
||||||
pub async fn disconnect_monitor<N: NetworkZone>(
|
|
||||||
mut new_connection_rx: mpsc::UnboundedReceiver<(ConnectionHandle, InternalPeerID<N::Addr>)>,
|
|
||||||
client_pool: Arc<ClientPool<N>>,
|
|
||||||
) {
|
|
||||||
// We need to hold a weak reference otherwise the client pool and this would hold a reference to
|
|
||||||
// each other causing the pool to be leaked.
|
|
||||||
let weak_client_pool = Arc::downgrade(&client_pool);
|
|
||||||
drop(client_pool);
|
|
||||||
|
|
||||||
tracing::info!("Starting peer disconnect monitor.");
|
|
||||||
|
|
||||||
let mut futs: FuturesUnordered<PeerDisconnectFut<N>> = FuturesUnordered::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
Some((con_handle, peer_id)) = new_connection_rx.recv() => {
|
|
||||||
tracing::debug!("Monitoring {peer_id} for disconnect");
|
|
||||||
futs.push(PeerDisconnectFut {
|
|
||||||
closed_fut: con_handle.closed(),
|
|
||||||
peer_id: Some(peer_id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Some(peer_id) = futs.next() => {
|
|
||||||
tracing::debug!("{peer_id} has disconnected, removing from client pool.");
|
|
||||||
let Some(pool) = weak_client_pool.upgrade() else {
|
|
||||||
tracing::info!("Peer disconnect monitor shutting down.");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
pool.remove_client(&peer_id);
|
|
||||||
drop(pool);
|
|
||||||
}
|
|
||||||
else => {
|
|
||||||
tracing::info!("Peer disconnect monitor shutting down.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`Future`] that resolves when a peer disconnects.
|
|
||||||
#[pin_project::pin_project]
|
|
||||||
pub(crate) struct PeerDisconnectFut<N: NetworkZone> {
|
|
||||||
/// The inner [`Future`] that resolves when a peer disconnects.
|
|
||||||
#[pin]
|
|
||||||
pub(crate) closed_fut: WaitForCancellationFutureOwned,
|
|
||||||
/// The peers ID.
|
|
||||||
pub(crate) peer_id: Option<InternalPeerID<N::Addr>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: NetworkZone> Future for PeerDisconnectFut<N> {
|
|
||||||
type Output = InternalPeerID<N::Addr>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
|
|
||||||
this.closed_fut
|
|
||||||
.poll(cx)
|
|
||||||
.map(|()| this.peer_id.take().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
use std::{
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cuprate_p2p_core::{client::Client, NetworkZone};
|
|
||||||
|
|
||||||
use crate::client_pool::ClientPool;
|
|
||||||
|
|
||||||
/// A wrapper around [`Client`] which returns the client to the [`ClientPool`] when dropped.
|
|
||||||
pub struct ClientPoolDropGuard<N: NetworkZone> {
|
|
||||||
/// The [`ClientPool`] to return the peer to.
|
|
||||||
pub(super) pool: Arc<ClientPool<N>>,
|
|
||||||
/// The [`Client`].
|
|
||||||
///
|
|
||||||
/// This is set to [`Some`] when this guard is created, then
|
|
||||||
/// [`take`](Option::take)n and returned to the pool when dropped.
|
|
||||||
pub(super) client: Option<Client<N>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: NetworkZone> Deref for ClientPoolDropGuard<N> {
|
|
||||||
type Target = Client<N>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.client.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: NetworkZone> DerefMut for ClientPoolDropGuard<N> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.client.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: NetworkZone> Drop for ClientPoolDropGuard<N> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let client = self.client.take().unwrap();
|
|
||||||
|
|
||||||
self.pool.add_client(client);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,6 @@ use cuprate_p2p_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client_pool::ClientPool,
|
|
||||||
config::P2PConfig,
|
config::P2PConfig,
|
||||||
constants::{HANDSHAKE_TIMEOUT, MAX_SEED_CONNECTIONS, OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT},
|
constants::{HANDSHAKE_TIMEOUT, MAX_SEED_CONNECTIONS, OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT},
|
||||||
};
|
};
|
||||||
|
@ -46,7 +45,7 @@ pub struct MakeConnectionRequest {
|
||||||
/// This handles maintaining a minimum number of connections and making extra connections when needed, upto a maximum.
|
/// This handles maintaining a minimum number of connections and making extra connections when needed, upto a maximum.
|
||||||
pub struct OutboundConnectionKeeper<N: NetworkZone, A, C> {
|
pub struct OutboundConnectionKeeper<N: NetworkZone, A, C> {
|
||||||
/// The pool of currently connected peers.
|
/// The pool of currently connected peers.
|
||||||
pub client_pool: Arc<ClientPool<N>>,
|
pub new_peers_tx: mpsc::Sender<Client<N>>,
|
||||||
/// The channel that tells us to make new _extra_ outbound connections.
|
/// The channel that tells us to make new _extra_ outbound connections.
|
||||||
pub make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
|
pub make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
|
||||||
/// The address book service
|
/// The address book service
|
||||||
|
@ -77,7 +76,7 @@ where
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: P2PConfig<N>,
|
config: P2PConfig<N>,
|
||||||
client_pool: Arc<ClientPool<N>>,
|
new_peers_tx: mpsc::Sender<Client<N>>,
|
||||||
make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
|
make_connection_rx: mpsc::Receiver<MakeConnectionRequest>,
|
||||||
address_book_svc: A,
|
address_book_svc: A,
|
||||||
connector_svc: C,
|
connector_svc: C,
|
||||||
|
@ -86,7 +85,7 @@ where
|
||||||
.expect("Gray peer percent is incorrect should be 0..=1");
|
.expect("Gray peer percent is incorrect should be 0..=1");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client_pool,
|
new_peers_tx,
|
||||||
make_connection_rx,
|
make_connection_rx,
|
||||||
address_book_svc,
|
address_book_svc,
|
||||||
connector_svc,
|
connector_svc,
|
||||||
|
@ -149,7 +148,7 @@ where
|
||||||
/// Connects to a given outbound peer.
|
/// Connects to a given outbound peer.
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn connect_to_outbound_peer(&mut self, permit: OwnedSemaphorePermit, addr: N::Addr) {
|
async fn connect_to_outbound_peer(&mut self, permit: OwnedSemaphorePermit, addr: N::Addr) {
|
||||||
let client_pool = Arc::clone(&self.client_pool);
|
let new_peers_tx = self.new_peers_tx.clone();
|
||||||
let connection_fut = self
|
let connection_fut = self
|
||||||
.connector_svc
|
.connector_svc
|
||||||
.ready()
|
.ready()
|
||||||
|
@ -164,7 +163,7 @@ where
|
||||||
async move {
|
async move {
|
||||||
#[expect(clippy::significant_drop_in_scrutinee)]
|
#[expect(clippy::significant_drop_in_scrutinee)]
|
||||||
if let Ok(Ok(peer)) = timeout(HANDSHAKE_TIMEOUT, connection_fut).await {
|
if let Ok(Ok(peer)) = timeout(HANDSHAKE_TIMEOUT, connection_fut).await {
|
||||||
client_pool.add_new_client(peer);
|
drop(new_peers_tx.send(peer).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.instrument(Span::current()),
|
.instrument(Span::current()),
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub(crate) const INITIAL_CHAIN_REQUESTS_TO_SEND: usize = 3;
|
||||||
/// The enforced maximum amount of blocks to request in a batch.
|
/// The enforced maximum amount of blocks to request in a batch.
|
||||||
///
|
///
|
||||||
/// Requesting more than this will cause the peer to disconnect and potentially lead to bans.
|
/// Requesting more than this will cause the peer to disconnect and potentially lead to bans.
|
||||||
pub(crate) const MAX_BLOCK_BATCH_LEN: usize = 100;
|
pub const MAX_BLOCK_BATCH_LEN: usize = 100;
|
||||||
|
|
||||||
/// The timeout that the block downloader will use for requests.
|
/// The timeout that the block downloader will use for requests.
|
||||||
pub(crate) const BLOCK_DOWNLOADER_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
|
pub(crate) const BLOCK_DOWNLOADER_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
@ -61,13 +61,13 @@ pub(crate) const BLOCK_DOWNLOADER_REQUEST_TIMEOUT: Duration = Duration::from_sec
|
||||||
/// be less than.
|
/// be less than.
|
||||||
///
|
///
|
||||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions.html#transaction-size>
|
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions.html#transaction-size>
|
||||||
pub(crate) const MAX_TRANSACTION_BLOB_SIZE: usize = 1_000_000;
|
pub const MAX_TRANSACTION_BLOB_SIZE: usize = 1_000_000;
|
||||||
|
|
||||||
/// The maximum amount of block IDs allowed in a chain entry response.
|
/// The maximum amount of block IDs allowed in a chain entry response.
|
||||||
///
|
///
|
||||||
/// ref: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_config.h#L97>
|
/// ref: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_config.h#L97>
|
||||||
// TODO: link to the protocol book when this section is added.
|
// TODO: link to the protocol book when this section is added.
|
||||||
pub(crate) const MAX_BLOCKS_IDS_IN_CHAIN_ENTRY: usize = 25_000;
|
pub const MAX_BLOCKS_IDS_IN_CHAIN_ENTRY: usize = 25_000;
|
||||||
|
|
||||||
/// The amount of failures downloading a specific batch before we stop attempting to download it.
|
/// The amount of failures downloading a specific batch before we stop attempting to download it.
|
||||||
pub(crate) const MAX_DOWNLOAD_FAILURES: usize = 5;
|
pub(crate) const MAX_DOWNLOAD_FAILURES: usize = 5;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{pin::pin, sync::Arc};
|
||||||
|
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::Semaphore,
|
sync::{mpsc, Semaphore},
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
time::{sleep, timeout},
|
time::{sleep, timeout},
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@ use cuprate_wire::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client_pool::ClientPool,
|
|
||||||
constants::{
|
constants::{
|
||||||
HANDSHAKE_TIMEOUT, INBOUND_CONNECTION_COOL_DOWN, PING_REQUEST_CONCURRENCY,
|
HANDSHAKE_TIMEOUT, INBOUND_CONNECTION_COOL_DOWN, PING_REQUEST_CONCURRENCY,
|
||||||
PING_REQUEST_TIMEOUT,
|
PING_REQUEST_TIMEOUT,
|
||||||
|
@ -36,7 +35,7 @@ use crate::{
|
||||||
/// and initiate handshake if needed, after verifying the address isn't banned.
|
/// and initiate handshake if needed, after verifying the address isn't banned.
|
||||||
#[instrument(level = "warn", skip_all)]
|
#[instrument(level = "warn", skip_all)]
|
||||||
pub async fn inbound_server<N, HS, A>(
|
pub async fn inbound_server<N, HS, A>(
|
||||||
client_pool: Arc<ClientPool<N>>,
|
new_connection_tx: mpsc::Sender<Client<N>>,
|
||||||
mut handshaker: HS,
|
mut handshaker: HS,
|
||||||
mut address_book: A,
|
mut address_book: A,
|
||||||
config: P2PConfig<N>,
|
config: P2PConfig<N>,
|
||||||
|
@ -111,13 +110,13 @@ where
|
||||||
permit: Some(permit),
|
permit: Some(permit),
|
||||||
});
|
});
|
||||||
|
|
||||||
let cloned_pool = Arc::clone(&client_pool);
|
let new_connection_tx = new_connection_tx.clone();
|
||||||
|
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
async move {
|
async move {
|
||||||
let client = timeout(HANDSHAKE_TIMEOUT, fut).await;
|
let client = timeout(HANDSHAKE_TIMEOUT, fut).await;
|
||||||
if let Ok(Ok(peer)) = client {
|
if let Ok(Ok(peer)) = client {
|
||||||
cloned_pool.add_new_client(peer);
|
drop(new_connection_tx.send(peer).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.instrument(Span::current()),
|
.instrument(Span::current()),
|
||||||
|
|
|
@ -18,17 +18,18 @@ use cuprate_p2p_core::{
|
||||||
|
|
||||||
pub mod block_downloader;
|
pub mod block_downloader;
|
||||||
mod broadcast;
|
mod broadcast;
|
||||||
pub mod client_pool;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod connection_maintainer;
|
pub mod connection_maintainer;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
mod inbound_server;
|
mod inbound_server;
|
||||||
|
mod peer_set;
|
||||||
|
|
||||||
use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse};
|
use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse};
|
||||||
pub use broadcast::{BroadcastRequest, BroadcastSvc};
|
pub use broadcast::{BroadcastRequest, BroadcastSvc};
|
||||||
pub use client_pool::{ClientPool, ClientPoolDropGuard};
|
|
||||||
pub use config::{AddressBookConfig, P2PConfig};
|
pub use config::{AddressBookConfig, P2PConfig};
|
||||||
use connection_maintainer::MakeConnectionRequest;
|
use connection_maintainer::MakeConnectionRequest;
|
||||||
|
use peer_set::PeerSet;
|
||||||
|
pub use peer_set::{ClientDropGuard, PeerSetRequest, PeerSetResponse};
|
||||||
|
|
||||||
/// Initializes the P2P [`NetworkInterface`] for a specific [`NetworkZone`].
|
/// Initializes the P2P [`NetworkInterface`] for a specific [`NetworkZone`].
|
||||||
///
|
///
|
||||||
|
@ -54,7 +55,10 @@ where
|
||||||
cuprate_address_book::init_address_book(config.address_book_config.clone()).await?;
|
cuprate_address_book::init_address_book(config.address_book_config.clone()).await?;
|
||||||
let address_book = Buffer::new(
|
let address_book = Buffer::new(
|
||||||
address_book,
|
address_book,
|
||||||
config.max_inbound_connections + config.outbound_connections,
|
config
|
||||||
|
.max_inbound_connections
|
||||||
|
.checked_add(config.outbound_connections)
|
||||||
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use the default config. Changing the defaults affects tx fluff times, which could affect D++ so for now don't allow changing
|
// Use the default config. Changing the defaults affects tx fluff times, which could affect D++ so for now don't allow changing
|
||||||
|
@ -83,19 +87,25 @@ where
|
||||||
|
|
||||||
let outbound_handshaker = outbound_handshaker_builder.build();
|
let outbound_handshaker = outbound_handshaker_builder.build();
|
||||||
|
|
||||||
let client_pool = ClientPool::new();
|
let (new_connection_tx, new_connection_rx) = mpsc::channel(
|
||||||
|
config
|
||||||
|
.outbound_connections
|
||||||
|
.checked_add(config.max_inbound_connections)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
let (make_connection_tx, make_connection_rx) = mpsc::channel(3);
|
let (make_connection_tx, make_connection_rx) = mpsc::channel(3);
|
||||||
|
|
||||||
let outbound_connector = Connector::new(outbound_handshaker);
|
let outbound_connector = Connector::new(outbound_handshaker);
|
||||||
let outbound_connection_maintainer = connection_maintainer::OutboundConnectionKeeper::new(
|
let outbound_connection_maintainer = connection_maintainer::OutboundConnectionKeeper::new(
|
||||||
config.clone(),
|
config.clone(),
|
||||||
Arc::clone(&client_pool),
|
new_connection_tx.clone(),
|
||||||
make_connection_rx,
|
make_connection_rx,
|
||||||
address_book.clone(),
|
address_book.clone(),
|
||||||
outbound_connector,
|
outbound_connector,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let peer_set = PeerSet::new(new_connection_rx);
|
||||||
|
|
||||||
let mut background_tasks = JoinSet::new();
|
let mut background_tasks = JoinSet::new();
|
||||||
|
|
||||||
background_tasks.spawn(
|
background_tasks.spawn(
|
||||||
|
@ -105,7 +115,7 @@ where
|
||||||
);
|
);
|
||||||
background_tasks.spawn(
|
background_tasks.spawn(
|
||||||
inbound_server::inbound_server(
|
inbound_server::inbound_server(
|
||||||
Arc::clone(&client_pool),
|
new_connection_tx,
|
||||||
inbound_handshaker,
|
inbound_handshaker,
|
||||||
address_book.clone(),
|
address_book.clone(),
|
||||||
config,
|
config,
|
||||||
|
@ -121,7 +131,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(NetworkInterface {
|
Ok(NetworkInterface {
|
||||||
pool: client_pool,
|
peer_set: Buffer::new(peer_set, 10).boxed_clone(),
|
||||||
broadcast_svc,
|
broadcast_svc,
|
||||||
make_connection_tx,
|
make_connection_tx,
|
||||||
address_book: address_book.boxed_clone(),
|
address_book: address_book.boxed_clone(),
|
||||||
|
@ -133,7 +143,7 @@ where
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NetworkInterface<N: NetworkZone> {
|
pub struct NetworkInterface<N: NetworkZone> {
|
||||||
/// A pool of free connected peers.
|
/// A pool of free connected peers.
|
||||||
pool: Arc<ClientPool<N>>,
|
peer_set: BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError>,
|
||||||
/// A [`Service`] that allows broadcasting to all connected peers.
|
/// A [`Service`] that allows broadcasting to all connected peers.
|
||||||
broadcast_svc: BroadcastSvc<N>,
|
broadcast_svc: BroadcastSvc<N>,
|
||||||
/// A channel to request extra connections.
|
/// A channel to request extra connections.
|
||||||
|
@ -163,7 +173,7 @@ impl<N: NetworkZone> NetworkInterface<N> {
|
||||||
+ 'static,
|
+ 'static,
|
||||||
C::Future: Send + 'static,
|
C::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
block_downloader::download_blocks(Arc::clone(&self.pool), our_chain_service, config)
|
block_downloader::download_blocks(self.peer_set.clone(), our_chain_service, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the address book service.
|
/// Returns the address book service.
|
||||||
|
@ -173,8 +183,10 @@ impl<N: NetworkZone> NetworkInterface<N> {
|
||||||
self.address_book.clone()
|
self.address_book.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrows the `ClientPool`, for access to connected peers.
|
/// Borrows the `PeerSet`, for access to connected peers.
|
||||||
pub const fn client_pool(&self) -> &Arc<ClientPool<N>> {
|
pub fn peer_set(
|
||||||
&self.pool
|
&mut self,
|
||||||
|
) -> &mut BoxCloneService<PeerSetRequest, PeerSetResponse<N>, tower::BoxError> {
|
||||||
|
&mut self.peer_set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
217
p2p/p2p/src/peer_set.rs
Normal file
217
p2p/p2p/src/peer_set.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use std::{
|
||||||
|
future::{ready, Future, Ready},
|
||||||
|
pin::{pin, Pin},
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::{stream::FuturesUnordered, StreamExt};
|
||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
|
use rand::{seq::index::sample, thread_rng};
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use cuprate_helper::cast::u64_to_usize;
|
||||||
|
use cuprate_p2p_core::{
|
||||||
|
client::{Client, InternalPeerID},
|
||||||
|
ConnectionDirection, NetworkZone,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod client_wrappers;
|
||||||
|
|
||||||
|
pub use client_wrappers::ClientDropGuard;
|
||||||
|
use client_wrappers::StoredClient;
|
||||||
|
|
||||||
|
/// A request to the peer-set.
|
||||||
|
pub enum PeerSetRequest {
|
||||||
|
/// The most claimed proof-of-work from a peer in the peer-set.
|
||||||
|
MostPoWSeen,
|
||||||
|
/// Peers with more cumulative difficulty than the given cumulative difficulty.
|
||||||
|
///
|
||||||
|
/// Returned peers will be remembered and won't be returned from subsequent calls until the guard is dropped.
|
||||||
|
PeersWithMorePoW(u128),
|
||||||
|
/// A random outbound peer.
|
||||||
|
///
|
||||||
|
/// The returned peer will be remembered and won't be returned from subsequent calls until the guard is dropped.
|
||||||
|
StemPeer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A response from the peer-set.
|
||||||
|
pub enum PeerSetResponse<N: NetworkZone> {
|
||||||
|
/// [`PeerSetRequest::MostPoWSeen`]
|
||||||
|
MostPoWSeen {
|
||||||
|
/// The cumulative difficulty claimed.
|
||||||
|
cumulative_difficulty: u128,
|
||||||
|
/// The height claimed.
|
||||||
|
height: usize,
|
||||||
|
/// The claimed hash of the top block.
|
||||||
|
top_hash: [u8; 32],
|
||||||
|
},
|
||||||
|
/// [`PeerSetRequest::PeersWithMorePoW`]
|
||||||
|
///
|
||||||
|
/// Returned peers will be remembered and won't be returned from subsequent calls until the guard is dropped.
|
||||||
|
PeersWithMorePoW(Vec<ClientDropGuard<N>>),
|
||||||
|
/// [`PeerSetRequest::StemPeer`]
|
||||||
|
///
|
||||||
|
/// The returned peer will be remembered and won't be returned from subsequent calls until the guard is dropped.
|
||||||
|
StemPeer(Option<ClientDropGuard<N>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Future`] that completes when a peer disconnects.
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
struct ClosedConnectionFuture<N: NetworkZone> {
|
||||||
|
#[pin]
|
||||||
|
fut: WaitForCancellationFutureOwned,
|
||||||
|
id: Option<InternalPeerID<N::Addr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> Future for ClosedConnectionFuture<N> {
|
||||||
|
type Output = InternalPeerID<N::Addr>;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
this.fut.poll(cx).map(|()| this.id.take().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of all connected peers on a [`NetworkZone`].
|
||||||
|
pub(crate) struct PeerSet<N: NetworkZone> {
|
||||||
|
/// The connected peers.
|
||||||
|
peers: IndexMap<InternalPeerID<N::Addr>, StoredClient<N>>,
|
||||||
|
/// A [`FuturesUnordered`] that resolves when a peer disconnects.
|
||||||
|
closed_connections: FuturesUnordered<ClosedConnectionFuture<N>>,
|
||||||
|
/// The [`InternalPeerID`]s of all outbound peers.
|
||||||
|
outbound_peers: IndexSet<InternalPeerID<N::Addr>>,
|
||||||
|
/// A channel of new peers from the inbound server or outbound connector.
|
||||||
|
new_peers: Receiver<Client<N>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> PeerSet<N> {
|
||||||
|
pub(crate) fn new(new_peers: Receiver<Client<N>>) -> Self {
|
||||||
|
Self {
|
||||||
|
peers: IndexMap::new(),
|
||||||
|
closed_connections: FuturesUnordered::new(),
|
||||||
|
outbound_peers: IndexSet::new(),
|
||||||
|
new_peers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Polls the new peers channel for newly connected peers.
|
||||||
|
fn poll_new_peers(&mut self, cx: &mut Context<'_>) {
|
||||||
|
while let Poll::Ready(Some(new_peer)) = self.new_peers.poll_recv(cx) {
|
||||||
|
if new_peer.info.direction == ConnectionDirection::Outbound {
|
||||||
|
self.outbound_peers.insert(new_peer.info.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.closed_connections.push(ClosedConnectionFuture {
|
||||||
|
fut: new_peer.info.handle.closed(),
|
||||||
|
id: Some(new_peer.info.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.peers
|
||||||
|
.insert(new_peer.info.id, StoredClient::new(new_peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove disconnected peers from the peer set.
|
||||||
|
fn remove_dead_peers(&mut self, cx: &mut Context<'_>) {
|
||||||
|
while let Poll::Ready(Some(dead_peer)) = self.closed_connections.poll_next_unpin(cx) {
|
||||||
|
let Some(peer) = self.peers.swap_remove(&dead_peer) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if peer.client.info.direction == ConnectionDirection::Outbound {
|
||||||
|
self.outbound_peers.swap_remove(&peer.client.info.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.peers.swap_remove(&dead_peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`PeerSetRequest::MostPoWSeen`]
|
||||||
|
fn most_pow_seen(&self) -> PeerSetResponse<N> {
|
||||||
|
let most_pow_chain = self
|
||||||
|
.peers
|
||||||
|
.values()
|
||||||
|
.map(|peer| {
|
||||||
|
let core_sync_data = peer.client.info.core_sync_data.lock().unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
core_sync_data.cumulative_difficulty(),
|
||||||
|
u64_to_usize(core_sync_data.current_height),
|
||||||
|
core_sync_data.top_id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.max_by_key(|(cumulative_difficulty, ..)| *cumulative_difficulty)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
PeerSetResponse::MostPoWSeen {
|
||||||
|
cumulative_difficulty: most_pow_chain.0,
|
||||||
|
height: most_pow_chain.1,
|
||||||
|
top_hash: most_pow_chain.2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`PeerSetRequest::PeersWithMorePoW`]
|
||||||
|
fn peers_with_more_pow(&self, cumulative_difficulty: u128) -> PeerSetResponse<N> {
|
||||||
|
PeerSetResponse::PeersWithMorePoW(
|
||||||
|
self.peers
|
||||||
|
.values()
|
||||||
|
.filter(|&client| {
|
||||||
|
!client.is_downloading_blocks()
|
||||||
|
&& client
|
||||||
|
.client
|
||||||
|
.info
|
||||||
|
.core_sync_data
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.cumulative_difficulty()
|
||||||
|
> cumulative_difficulty
|
||||||
|
})
|
||||||
|
.map(StoredClient::downloading_blocks_guard)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`PeerSetRequest::StemPeer`]
|
||||||
|
fn random_peer_for_stem(&self) -> PeerSetResponse<N> {
|
||||||
|
PeerSetResponse::StemPeer(
|
||||||
|
sample(
|
||||||
|
&mut thread_rng(),
|
||||||
|
self.outbound_peers.len(),
|
||||||
|
self.outbound_peers.len(),
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|i| {
|
||||||
|
let peer = self.outbound_peers.get_index(i).unwrap();
|
||||||
|
let client = self.peers.get(peer).unwrap();
|
||||||
|
(!client.is_a_stem_peer()).then(|| client.stem_peer_guard())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> Service<PeerSetRequest> for PeerSet<N> {
|
||||||
|
type Response = PeerSetResponse<N>;
|
||||||
|
type Error = tower::BoxError;
|
||||||
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.poll_new_peers(cx);
|
||||||
|
self.remove_dead_peers(cx);
|
||||||
|
|
||||||
|
// TODO: should we return `Pending` if we don't have any peers?
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: PeerSetRequest) -> Self::Future {
|
||||||
|
ready(match req {
|
||||||
|
PeerSetRequest::MostPoWSeen => Ok(self.most_pow_seen()),
|
||||||
|
PeerSetRequest::PeersWithMorePoW(cumulative_difficulty) => {
|
||||||
|
Ok(self.peers_with_more_pow(cumulative_difficulty))
|
||||||
|
}
|
||||||
|
PeerSetRequest::StemPeer => Ok(self.random_peer_for_stem()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
86
p2p/p2p/src/peer_set/client_wrappers.rs
Normal file
86
p2p/p2p/src/peer_set/client_wrappers.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use std::{
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use cuprate_p2p_core::{
|
||||||
|
client::{Client, WeakClient},
|
||||||
|
NetworkZone,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A client stored in the peer-set.
|
||||||
|
pub(super) struct StoredClient<N: NetworkZone> {
|
||||||
|
pub client: Client<N>,
|
||||||
|
/// An [`AtomicBool`] for if the peer is currently downloading blocks.
|
||||||
|
downloading_blocks: Arc<AtomicBool>,
|
||||||
|
/// An [`AtomicBool`] for if the peer is currently being used to stem txs.
|
||||||
|
stem_peer: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> StoredClient<N> {
|
||||||
|
pub(super) fn new(client: Client<N>) -> Self {
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
downloading_blocks: Arc::new(AtomicBool::new(false)),
|
||||||
|
stem_peer: Arc::new(AtomicBool::new(false)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [`true`] if the [`StoredClient`] is currently downloading blocks.
|
||||||
|
pub(super) fn is_downloading_blocks(&self) -> bool {
|
||||||
|
self.downloading_blocks.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [`true`] if the [`StoredClient`] is currently being used to stem txs.
|
||||||
|
pub(super) fn is_a_stem_peer(&self) -> bool {
|
||||||
|
self.stem_peer.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`ClientDropGuard`] that while it is alive keeps the [`StoredClient`] in the downloading blocks state.
|
||||||
|
pub(super) fn downloading_blocks_guard(&self) -> ClientDropGuard<N> {
|
||||||
|
self.downloading_blocks.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
ClientDropGuard {
|
||||||
|
client: self.client.downgrade(),
|
||||||
|
bool: Arc::clone(&self.downloading_blocks),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`ClientDropGuard`] that while it is alive keeps the [`StoredClient`] in the stemming peers state.
|
||||||
|
pub(super) fn stem_peer_guard(&self) -> ClientDropGuard<N> {
|
||||||
|
self.stem_peer.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
ClientDropGuard {
|
||||||
|
client: self.client.downgrade(),
|
||||||
|
bool: Arc::clone(&self.stem_peer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Drop`] guard for a client returned from the peer-set.
|
||||||
|
pub struct ClientDropGuard<N: NetworkZone> {
|
||||||
|
client: WeakClient<N>,
|
||||||
|
bool: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> Deref for ClientDropGuard<N> {
|
||||||
|
type Target = WeakClient<N>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> DerefMut for ClientDropGuard<N> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: NetworkZone> Drop for ClientDropGuard<N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.bool.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
|
@ -327,7 +327,7 @@ impl DecompressedPruningSeed {
|
||||||
///
|
///
|
||||||
/// This function will also error if `block_height` > `blockchain_height`
|
/// This function will also error if `block_height` > `blockchain_height`
|
||||||
///
|
///
|
||||||
pub fn get_next_unpruned_block(
|
pub const fn get_next_unpruned_block(
|
||||||
&self,
|
&self,
|
||||||
block_height: usize,
|
block_height: usize,
|
||||||
blockchain_height: usize,
|
blockchain_height: usize,
|
||||||
|
|
|
@ -68,7 +68,7 @@ macro_rules! generate_endpoints_with_no_input {
|
||||||
/// - [`generate_endpoints_with_input`]
|
/// - [`generate_endpoints_with_input`]
|
||||||
/// - [`generate_endpoints_with_no_input`]
|
/// - [`generate_endpoints_with_no_input`]
|
||||||
macro_rules! generate_endpoints_inner {
|
macro_rules! generate_endpoints_inner {
|
||||||
($variant:ident, $handler:ident, $request:expr) => {
|
($variant:ident, $handler:ident, $request:expr_2021) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
{
|
{
|
||||||
// Check if restricted.
|
// Check if restricted.
|
||||||
|
|
|
@ -71,7 +71,7 @@ macro_rules! generate_endpoints_with_no_input {
|
||||||
/// - [`generate_endpoints_with_input`]
|
/// - [`generate_endpoints_with_input`]
|
||||||
/// - [`generate_endpoints_with_no_input`]
|
/// - [`generate_endpoints_with_no_input`]
|
||||||
macro_rules! generate_endpoints_inner {
|
macro_rules! generate_endpoints_inner {
|
||||||
($variant:ident, $handler:ident, $request:expr) => {
|
($variant:ident, $handler:ident, $request:expr_2021) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
{
|
{
|
||||||
// Check if restricted.
|
// Check if restricted.
|
||||||
|
|
|
@ -9,26 +9,19 @@ use cuprate_fixed_bytes::ByteArrayVec;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
#[cfg(feature = "epee")]
|
||||||
use cuprate_epee_encoding::{
|
use cuprate_epee_encoding::container_as_blob::ContainerAsBlob;
|
||||||
container_as_blob::ContainerAsBlob,
|
|
||||||
epee_object, error,
|
|
||||||
macros::bytes::{Buf, BufMut},
|
|
||||||
read_epee_value, write_field, EpeeObject, EpeeObjectBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cuprate_types::BlockCompleteEntry;
|
use cuprate_types::BlockCompleteEntry;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::AccessResponseBase,
|
base::AccessResponseBase,
|
||||||
macros::{define_request, define_request_and_response, define_request_and_response_doc},
|
macros::define_request_and_response,
|
||||||
misc::{BlockOutputIndices, GetOutputsOut, OutKeyBin, PoolTxInfo, Status},
|
misc::{BlockOutputIndices, GetOutputsOut, OutKeyBin, PoolInfo},
|
||||||
rpc_call::RpcCallValue,
|
rpc_call::RpcCallValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(feature = "epee", feature = "serde"))]
|
#[cfg(any(feature = "epee", feature = "serde"))]
|
||||||
use crate::defaults::{default_false, default_zero};
|
use crate::defaults::{default_false, default_zero};
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
use crate::misc::PoolInfoExtent;
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Definitions
|
//---------------------------------------------------------------------------------------------------- Definitions
|
||||||
define_request_and_response! {
|
define_request_and_response! {
|
||||||
|
@ -115,15 +108,14 @@ define_request_and_response! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- GetBlocks
|
define_request_and_response! {
|
||||||
define_request! {
|
get_blocksbin,
|
||||||
#[doc = define_request_and_response_doc!(
|
cc73fe71162d564ffda8e549b79a350bca53c454 =>
|
||||||
"response" => GetBlocksResponse,
|
core_rpc_server_commands_defs.h => 162..=262,
|
||||||
get_blocksbin,
|
|
||||||
cc73fe71162d564ffda8e549b79a350bca53c454,
|
GetBlocks,
|
||||||
core_rpc_server_commands_defs, h, 162, 262,
|
|
||||||
)]
|
Request {
|
||||||
GetBlocksRequest {
|
|
||||||
requested_info: u8 = default_zero::<u8>(), "default_zero",
|
requested_info: u8 = default_zero::<u8>(), "default_zero",
|
||||||
// FIXME: This is a `std::list` in `monerod` because...?
|
// FIXME: This is a `std::list` in `monerod` because...?
|
||||||
block_ids: ByteArrayVec<32>,
|
block_ids: ByteArrayVec<32>,
|
||||||
|
@ -131,259 +123,17 @@ define_request! {
|
||||||
prune: bool,
|
prune: bool,
|
||||||
no_miner_tx: bool = default_false(), "default_false",
|
no_miner_tx: bool = default_false(), "default_false",
|
||||||
pool_info_since: u64 = default_zero::<u64>(), "default_zero",
|
pool_info_since: u64 = default_zero::<u64>(), "default_zero",
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
#[doc = define_request_and_response_doc!(
|
// TODO: add `top_block_hash` field
|
||||||
"request" => GetBlocksRequest,
|
// <https://github.com/monero-project/monero/blame/893916ad091a92e765ce3241b94e706ad012b62a/src/rpc/core_rpc_server_commands_defs.h#L263>
|
||||||
get_blocksbin,
|
AccessResponseBase {
|
||||||
cc73fe71162d564ffda8e549b79a350bca53c454,
|
blocks: Vec<BlockCompleteEntry>,
|
||||||
core_rpc_server_commands_defs, h, 162, 262,
|
start_height: u64,
|
||||||
)]
|
current_height: u64,
|
||||||
///
|
output_indices: Vec<BlockOutputIndices>,
|
||||||
/// This response's variant depends upon [`PoolInfoExtent`].
|
daemon_time: u64,
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
pool_info: PoolInfo,
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum GetBlocksResponse {
|
|
||||||
/// Will always serialize a [`PoolInfoExtent::None`] field.
|
|
||||||
PoolInfoNone(GetBlocksResponsePoolInfoNone),
|
|
||||||
/// Will always serialize a [`PoolInfoExtent::Incremental`] field.
|
|
||||||
PoolInfoIncremental(GetBlocksResponsePoolInfoIncremental),
|
|
||||||
/// Will always serialize a [`PoolInfoExtent::Full`] field.
|
|
||||||
PoolInfoFull(GetBlocksResponsePoolInfoFull),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GetBlocksResponse {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::PoolInfoNone(GetBlocksResponsePoolInfoNone::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data within [`GetBlocksResponse::PoolInfoNone`].
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct GetBlocksResponsePoolInfoNone {
|
|
||||||
pub status: Status,
|
|
||||||
pub untrusted: bool,
|
|
||||||
pub blocks: Vec<BlockCompleteEntry>,
|
|
||||||
pub start_height: u64,
|
|
||||||
pub current_height: u64,
|
|
||||||
pub output_indices: Vec<BlockOutputIndices>,
|
|
||||||
pub daemon_time: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
epee_object! {
|
|
||||||
GetBlocksResponsePoolInfoNone,
|
|
||||||
status: Status,
|
|
||||||
untrusted: bool,
|
|
||||||
blocks: Vec<BlockCompleteEntry>,
|
|
||||||
start_height: u64,
|
|
||||||
current_height: u64,
|
|
||||||
output_indices: Vec<BlockOutputIndices>,
|
|
||||||
daemon_time: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data within [`GetBlocksResponse::PoolInfoIncremental`].
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct GetBlocksResponsePoolInfoIncremental {
|
|
||||||
pub status: Status,
|
|
||||||
pub untrusted: bool,
|
|
||||||
pub blocks: Vec<BlockCompleteEntry>,
|
|
||||||
pub start_height: u64,
|
|
||||||
pub current_height: u64,
|
|
||||||
pub output_indices: Vec<BlockOutputIndices>,
|
|
||||||
pub daemon_time: u64,
|
|
||||||
pub added_pool_txs: Vec<PoolTxInfo>,
|
|
||||||
pub remaining_added_pool_txids: ByteArrayVec<32>,
|
|
||||||
pub removed_pool_txids: ByteArrayVec<32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
epee_object! {
|
|
||||||
GetBlocksResponsePoolInfoIncremental,
|
|
||||||
status: Status,
|
|
||||||
untrusted: bool,
|
|
||||||
blocks: Vec<BlockCompleteEntry>,
|
|
||||||
start_height: u64,
|
|
||||||
current_height: u64,
|
|
||||||
output_indices: Vec<BlockOutputIndices>,
|
|
||||||
daemon_time: u64,
|
|
||||||
added_pool_txs: Vec<PoolTxInfo>,
|
|
||||||
remaining_added_pool_txids: ByteArrayVec<32>,
|
|
||||||
removed_pool_txids: ByteArrayVec<32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data within [`GetBlocksResponse::PoolInfoFull`].
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct GetBlocksResponsePoolInfoFull {
|
|
||||||
pub status: Status,
|
|
||||||
pub untrusted: bool,
|
|
||||||
pub blocks: Vec<BlockCompleteEntry>,
|
|
||||||
pub start_height: u64,
|
|
||||||
pub current_height: u64,
|
|
||||||
pub output_indices: Vec<BlockOutputIndices>,
|
|
||||||
pub daemon_time: u64,
|
|
||||||
pub added_pool_txs: Vec<PoolTxInfo>,
|
|
||||||
pub remaining_added_pool_txids: ByteArrayVec<32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
epee_object! {
|
|
||||||
GetBlocksResponsePoolInfoFull,
|
|
||||||
status: Status,
|
|
||||||
untrusted: bool,
|
|
||||||
blocks: Vec<BlockCompleteEntry>,
|
|
||||||
start_height: u64,
|
|
||||||
current_height: u64,
|
|
||||||
output_indices: Vec<BlockOutputIndices>,
|
|
||||||
daemon_time: u64,
|
|
||||||
added_pool_txs: Vec<PoolTxInfo>,
|
|
||||||
remaining_added_pool_txids: ByteArrayVec<32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
/// [`EpeeObjectBuilder`] for [`GetBlocksResponse`].
|
|
||||||
///
|
|
||||||
/// Not for public usage.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct __GetBlocksResponseEpeeBuilder {
|
|
||||||
pub status: Option<Status>,
|
|
||||||
pub untrusted: Option<bool>,
|
|
||||||
pub blocks: Option<Vec<BlockCompleteEntry>>,
|
|
||||||
pub start_height: Option<u64>,
|
|
||||||
pub current_height: Option<u64>,
|
|
||||||
pub output_indices: Option<Vec<BlockOutputIndices>>,
|
|
||||||
pub daemon_time: Option<u64>,
|
|
||||||
pub pool_info_extent: Option<PoolInfoExtent>,
|
|
||||||
pub added_pool_txs: Option<Vec<PoolTxInfo>>,
|
|
||||||
pub remaining_added_pool_txids: Option<ByteArrayVec<32>>,
|
|
||||||
pub removed_pool_txids: Option<ByteArrayVec<32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
impl EpeeObjectBuilder<GetBlocksResponse> for __GetBlocksResponseEpeeBuilder {
|
|
||||||
fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> error::Result<bool> {
|
|
||||||
macro_rules! read_epee_field {
|
|
||||||
($($field:ident),*) => {
|
|
||||||
match name {
|
|
||||||
$(
|
|
||||||
stringify!($field) => { self.$field = Some(read_epee_value(r)?); },
|
|
||||||
)*
|
|
||||||
_ => return Ok(false),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
read_epee_field! {
|
|
||||||
status,
|
|
||||||
untrusted,
|
|
||||||
blocks,
|
|
||||||
start_height,
|
|
||||||
current_height,
|
|
||||||
output_indices,
|
|
||||||
daemon_time,
|
|
||||||
pool_info_extent,
|
|
||||||
added_pool_txs,
|
|
||||||
remaining_added_pool_txids,
|
|
||||||
removed_pool_txids
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(self) -> error::Result<GetBlocksResponse> {
|
|
||||||
const ELSE: error::Error = error::Error::Format("Required field was not found!");
|
|
||||||
|
|
||||||
let status = self.status.ok_or(ELSE)?;
|
|
||||||
let untrusted = self.untrusted.ok_or(ELSE)?;
|
|
||||||
let blocks = self.blocks.ok_or(ELSE)?;
|
|
||||||
let start_height = self.start_height.ok_or(ELSE)?;
|
|
||||||
let current_height = self.current_height.ok_or(ELSE)?;
|
|
||||||
let output_indices = self.output_indices.ok_or(ELSE)?;
|
|
||||||
let daemon_time = self.daemon_time.ok_or(ELSE)?;
|
|
||||||
let pool_info_extent = self.pool_info_extent.ok_or(ELSE)?;
|
|
||||||
|
|
||||||
let this = match pool_info_extent {
|
|
||||||
PoolInfoExtent::None => {
|
|
||||||
GetBlocksResponse::PoolInfoNone(GetBlocksResponsePoolInfoNone {
|
|
||||||
status,
|
|
||||||
untrusted,
|
|
||||||
blocks,
|
|
||||||
start_height,
|
|
||||||
current_height,
|
|
||||||
output_indices,
|
|
||||||
daemon_time,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
PoolInfoExtent::Incremental => {
|
|
||||||
GetBlocksResponse::PoolInfoIncremental(GetBlocksResponsePoolInfoIncremental {
|
|
||||||
status,
|
|
||||||
untrusted,
|
|
||||||
blocks,
|
|
||||||
start_height,
|
|
||||||
current_height,
|
|
||||||
output_indices,
|
|
||||||
daemon_time,
|
|
||||||
added_pool_txs: self.added_pool_txs.ok_or(ELSE)?,
|
|
||||||
remaining_added_pool_txids: self.remaining_added_pool_txids.ok_or(ELSE)?,
|
|
||||||
removed_pool_txids: self.removed_pool_txids.ok_or(ELSE)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
PoolInfoExtent::Full => {
|
|
||||||
GetBlocksResponse::PoolInfoFull(GetBlocksResponsePoolInfoFull {
|
|
||||||
status,
|
|
||||||
untrusted,
|
|
||||||
blocks,
|
|
||||||
start_height,
|
|
||||||
current_height,
|
|
||||||
output_indices,
|
|
||||||
daemon_time,
|
|
||||||
added_pool_txs: self.added_pool_txs.ok_or(ELSE)?,
|
|
||||||
remaining_added_pool_txids: self.remaining_added_pool_txids.ok_or(ELSE)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "epee")]
|
|
||||||
impl EpeeObject for GetBlocksResponse {
|
|
||||||
type Builder = __GetBlocksResponseEpeeBuilder;
|
|
||||||
|
|
||||||
fn number_of_fields(&self) -> u64 {
|
|
||||||
// [`PoolInfoExtent`] + inner struct fields.
|
|
||||||
let inner_fields = match self {
|
|
||||||
Self::PoolInfoNone(s) => s.number_of_fields(),
|
|
||||||
Self::PoolInfoIncremental(s) => s.number_of_fields(),
|
|
||||||
Self::PoolInfoFull(s) => s.number_of_fields(),
|
|
||||||
};
|
|
||||||
|
|
||||||
1 + inner_fields
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fields<B: BufMut>(self, w: &mut B) -> error::Result<()> {
|
|
||||||
match self {
|
|
||||||
Self::PoolInfoNone(s) => {
|
|
||||||
s.write_fields(w)?;
|
|
||||||
write_field(PoolInfoExtent::None.to_u8(), "pool_info_extent", w)?;
|
|
||||||
}
|
|
||||||
Self::PoolInfoIncremental(s) => {
|
|
||||||
s.write_fields(w)?;
|
|
||||||
write_field(PoolInfoExtent::Incremental.to_u8(), "pool_info_extent", w)?;
|
|
||||||
}
|
|
||||||
Self::PoolInfoFull(s) => {
|
|
||||||
s.write_fields(w)?;
|
|
||||||
write_field(PoolInfoExtent::Full.to_u8(), "pool_info_extent", w)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ macro_rules! serde_doc_test {
|
||||||
(
|
(
|
||||||
// `const` string from `cuprate_test_utils::rpc::data`
|
// `const` string from `cuprate_test_utils::rpc::data`
|
||||||
// v
|
// v
|
||||||
$cuprate_test_utils_rpc_const:ident => $expected:expr
|
$cuprate_test_utils_rpc_const:ident => $expected:expr_2021
|
||||||
// ^
|
// ^
|
||||||
// Expected value as an expression
|
// Expected value as an expression
|
||||||
) => {
|
) => {
|
||||||
|
|
|
@ -77,7 +77,7 @@ macro_rules! define_request_and_response {
|
||||||
$( #[$request_field_attr:meta] )* // Field attribute.
|
$( #[$request_field_attr:meta] )* // Field attribute.
|
||||||
$request_field:ident: $request_field_type:ty // field_name: field type
|
$request_field:ident: $request_field_type:ty // field_name: field type
|
||||||
$(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization
|
$(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization
|
||||||
$(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value
|
$(= $request_field_type_default:expr_2021, $request_field_type_default_string:literal)?, // (optional) default value
|
||||||
)*
|
)*
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ macro_rules! define_request_and_response {
|
||||||
$( #[$response_field_attr:meta] )*
|
$( #[$response_field_attr:meta] )*
|
||||||
$response_field:ident: $response_field_type:ty
|
$response_field:ident: $response_field_type:ty
|
||||||
$(as $response_field_type_as:ty)?
|
$(as $response_field_type_as:ty)?
|
||||||
$(= $response_field_type_default:expr, $response_field_type_default_string:literal)?,
|
$(= $response_field_type_default:expr_2021, $response_field_type_default_string:literal)?,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => { paste::paste! {
|
) => { paste::paste! {
|
||||||
|
@ -229,7 +229,7 @@ macro_rules! define_request {
|
||||||
// field_name: FieldType
|
// field_name: FieldType
|
||||||
$field:ident: $field_type:ty
|
$field:ident: $field_type:ty
|
||||||
$(as $field_as:ty)?
|
$(as $field_as:ty)?
|
||||||
$(= $field_default:expr, $field_default_string:literal)?,
|
$(= $field_default:expr_2021, $field_default_string:literal)?,
|
||||||
// The $field_default is an optional extra token that represents
|
// The $field_default is an optional extra token that represents
|
||||||
// a default value to pass to [`cuprate_epee_encoding::epee_object`],
|
// a default value to pass to [`cuprate_epee_encoding::epee_object`],
|
||||||
// see it for usage.
|
// see it for usage.
|
||||||
|
@ -286,7 +286,7 @@ macro_rules! define_response {
|
||||||
$( #[$field_attr:meta] )*
|
$( #[$field_attr:meta] )*
|
||||||
$field:ident: $field_type:ty
|
$field:ident: $field_type:ty
|
||||||
$(as $field_as:ty)?
|
$(as $field_as:ty)?
|
||||||
$(= $field_default:expr, $field_default_string:literal)?,
|
$(= $field_default:expr_2021, $field_default_string:literal)?,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
@ -323,7 +323,7 @@ macro_rules! define_response {
|
||||||
$( #[$field_attr:meta] )*
|
$( #[$field_attr:meta] )*
|
||||||
$field:ident: $field_type:ty
|
$field:ident: $field_type:ty
|
||||||
$(as $field_as:ty)?
|
$(as $field_as:ty)?
|
||||||
$(= $field_default:expr, $field_default_string:literal)?,
|
$(= $field_default:expr_2021, $field_default_string:literal)?,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
|
|
@ -11,11 +11,11 @@ use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "epee")]
|
#[cfg(feature = "epee")]
|
||||||
use cuprate_epee_encoding::epee_object;
|
use cuprate_epee_encoding::epee_object;
|
||||||
|
|
||||||
use crate::macros::monero_definition_link;
|
|
||||||
|
|
||||||
#[cfg(any(feature = "epee", feature = "serde"))]
|
#[cfg(any(feature = "epee", feature = "serde"))]
|
||||||
use crate::defaults::default_zero;
|
use crate::defaults::default_zero;
|
||||||
|
|
||||||
|
use crate::macros::monero_definition_link;
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Macros
|
//---------------------------------------------------------------------------------------------------- Macros
|
||||||
/// This macro (local to this file) defines all the misc types.
|
/// This macro (local to this file) defines all the misc types.
|
||||||
///
|
///
|
||||||
|
@ -37,7 +37,7 @@ macro_rules! define_struct_and_impl_epee {
|
||||||
$(
|
$(
|
||||||
$( #[$field_attr:meta] )* // Field attributes
|
$( #[$field_attr:meta] )* // Field attributes
|
||||||
// Field name => the type => optional `epee_object` default value.
|
// Field name => the type => optional `epee_object` default value.
|
||||||
$field_name:ident: $field_type:ty $(= $field_default:expr)?,
|
$field_name:ident: $field_type:ty $(= $field_default:expr_2021)?,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ mod distribution;
|
||||||
mod key_image_spent_status;
|
mod key_image_spent_status;
|
||||||
#[expect(clippy::module_inception)]
|
#[expect(clippy::module_inception)]
|
||||||
mod misc;
|
mod misc;
|
||||||
|
mod pool_info;
|
||||||
mod pool_info_extent;
|
mod pool_info_extent;
|
||||||
mod status;
|
mod status;
|
||||||
mod tx_entry;
|
mod tx_entry;
|
||||||
|
@ -30,6 +31,7 @@ pub use misc::{
|
||||||
OutputDistributionData, Peer, PoolTxInfo, PublicNode, SetBan, Span, SpentKeyImageInfo,
|
OutputDistributionData, Peer, PoolTxInfo, PublicNode, SetBan, Span, SpentKeyImageInfo,
|
||||||
SyncInfoPeer, TxBacklogEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats,
|
SyncInfoPeer, TxBacklogEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats,
|
||||||
};
|
};
|
||||||
|
pub use pool_info::PoolInfo;
|
||||||
pub use pool_info_extent::PoolInfoExtent;
|
pub use pool_info_extent::PoolInfoExtent;
|
||||||
pub use status::Status;
|
pub use status::Status;
|
||||||
pub use tx_entry::TxEntry;
|
pub use tx_entry::TxEntry;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue